为所有人提供TSC频率:更精准的性能分析与基准测试

本文介绍了tsc_freq_khz内核模块,解决了在虚拟化环境中LLVM X-Ray等性能分析工具无法准确获取TSC频率的问题,通过导出内核tsc_khz变量到用户空间,提升了性能测量的准确性。

为所有人提供TSC频率:更精准的性能分析与基准测试

您是否曾尝试使用LLVM的X-Ray性能分析工具生成火焰图,却遇到诸如以下的晦涩错误:

1
2
==65892==Unable to determine CPU frequency for TSC accounting.
==65892==Unable to determine CPU frequency.

或者更糟的是,当您分析了应用程序中的每个函数后,发现所有函数运行时间的总和仅占20分钟运行时间的约15分钟?那丢失的5分钟去哪儿了?

我们遇到了这两种情况,因此我们构建了一个名为tsc_freq_khz的Linux内核模块作为解决方案。我们将快速但深入地探讨x86计时器来解释这个问题,但首先,让我们看看这个模块的好处。

tsc_freq_khz模块增强了在虚拟化环境中使用X-Ray等性能分析和基准测试工具的性能。不再出现“无法确定CPU频率”的错误!

tsc_freq_khz模块还使这些工具在更新的Intel处理器上更加准确。X-Ray使用x86时间戳计数器(TSC),一种低延迟单调递增时钟,来测量事件持续时间,并假设TSC频率等同于最大时钟速度。这个假设在更新的CPU上是错误的,会导致时间测量不准确。tsc_freq_khz模块提供了一种读取实际TSC频率的方法。您的性能分析数据中不再丢失分钟!

tsc_freq_khz模块的工作原理是通过sysfs将Linux内核内部的x86时间戳计数器(TSC)频率值tsc_khz导出到用户空间。程序可以通过读取/sys/devices/system/cpu/cpu0/tsc_freq_khz来查询这个值。

一些开源项目,如X-Ray和Abseil,已经检查tsc_freq_khz的存在,但直到现在,这个文件仅存在于Google的生产内核中。tsc_freq_khz模块为其他所有人启用了TSC频率导出到用户空间。

时间戳的问题

在我们解释我们所做的事情以及为什么这有效之前,让我们快速介绍一下x86架构上的时间戳。

x86机器至少有六种不同的方法来测量时间:

  • 实时时钟(RTC)
  • 可编程间隔定时器(PIT)
  • 高性能事件定时器(HPET)
  • ACPI电源管理定时器(ACPI PM)
  • 高级可编程中断控制器(APIC)定时器
  • 当然,还有时间戳计数器(TSC)

每种方法都有独特而微妙的缺陷,使其在某些应用中完全不可行。这种计时器的丰富多样性是当你维护30年的向后兼容性并且从不删除一个功能时会发生的事情,因为某个地方有人依赖它。

尽管有许多缺陷,TSC对于基准测试和性能分析是有用的。它具有极低的延迟,因为电路直接位于CPU上,并且可以直接从用户模式应用程序访问。非常有用的性能分析工具,如X-Ray,依赖TSC进行准确测量。

然而,TSC以滴答测量时间,这些滴答在不同处理器之间不可比较,因此基本上没有意义。对于性能分析和基准测试,我们希望以可比较的单位(如纳秒)测量时间。

从滴答到纳秒

那么一个滴答中有多少纳秒?将某些持续时间的滴答转换为纳秒的基本公式很简单:

1
time_in_nanoseconds = (tsc_count_end - tsc_count_start) * tsc_frequency

不幸的是,确定TSC频率是困难的,并且在某些情况下,例如基于云或其他虚拟化环境中,是不可能的。直到现在。

核心问题是Linux内核没有提供应用程序知道TSC频率的方法,尽管Linux perf实用程序确实尝试通过Intel PT计算TSC频率。有不直接暴露该值的合理论点,因为TSC频率相当晦涩,并且在某些情况下该值完全没有意义。直到最近,处理器的最大时钟速度也是TSC频率的准确近似值。

然而,这不再成立。使用最大时钟速度作为TSC频率将在更新的Intel CPU上给出错误的结果。这是性能分析中丢失分钟的原因。

此外,在基于云或其他虚拟化环境中,无法访问最大时钟速度(在Linux中通过/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq访问)。这是“无法确定CPU频率”错误的原因。

cpuinfo_max_freq由用于频率缩放的cpufreq驱动程序填充——也就是说,根据省电设置使CPU运行得更快或更慢。自然,频率缩放在虚拟化环境中通常不允许,因为每个物理CPU与多个虚拟租户共享。因此,这个值不存在。

来自Google的提示

X-Ray中的时间戳测量代码引用了一个名为/sys/devices/system/cpu/cpu0/tsc_freq_khz的神秘sysfs条目,听起来它正好提供了我们需要的:以千赫兹为单位的TSC频率。不幸的是,在Linux内核源代码中绝对没有对该文件的引用。这是怎么回事?

更多搜索在Abseil源代码中揭示了以下提示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Google's production kernel has a patch to export the TSC
// frequency through sysfs. If the kernel is exporting the TSC

// cannot be relied on because the BIOS may be exporting an invalid
// p-state (on x86) or p-states may be used to put the processor in
// a new mode (turbo mode). Essentially, those frequencies cannot
// always be relied upon. The same reasons apply to /proc/cpuinfo as
// well.
if (ReadLongFromFile("/sys/devices/system/cpu/cpu0/tsc_freq_khz", &freq)) {
  return freq * 1e3;  // Value is kHz.
}

中奖了!注释告诉我们:

  • Google在生产中运行自定义Linux内核。
  • Google知道cpuinfo_max_freq不应用于基准测试。
  • 内核的TSC频率计算是合理的,并且
  • Google的内部内核有一个通过sysfs导出TSC频率的补丁,但该补丁尚未上游到主内核树。

为所有人提供TSC频率!

为什么只有Google内核可以访问TSC频率?由于Linux内核是开源的,我们可以编写自己的内核模块来做同样的事情。幸运的是,Linux内核已经在启动期间计算了TSC频率并将其存储在tsc_khz变量中。我们所要做的就是通过sysfs导出该变量。

我们的内核模块tsc_freq_khz正是这样做的。它创建了一个sysfs条目,读取由内核定义的tsc_khz变量,并通过/sys/devices/system/cpu/cpu0/tsc_freq_khz导出它。该模块非常简单但非常有用。

按照Github上的构建说明,并通过将其插入到您的内核中来测试模块:

1
2
3
4
5
$ sudo insmod ./tsc_freq_khz.ko
$ dmesg | grep tsc_freq_khz
[14045.345025] tsc_freq_khz: starting driver
[14045.345026] tsc_freq_khz: registering with sysfs
[14045.345028] tsc_freq_khz: successfully registered

现在应该填充/sys/devices/system/cpu/cpu0/tsc_freq_khz处的文件。(您系统上的值将不同。)

1
2
$ cat /sys/devices/system/cpu/cpu0/tsc_freq_khz
2712020

警告:请不要在真实生产系统中使用此代码。虽然它不应该引起问题,但它确实做了一些假设,例如CPU0存在。也没有健全性检查来警告TSC值是否不合理或不可靠。

结论

像这样看似简单的问题可以让我们深入一个迷人的兔子洞。随着时间的推移,Google的内部补丁可能会进入Linux内核,或者内核开发人员可能会创建自己的官方补丁来将TSC频率导出到用户空间。同时,对于希望使用LLVM优秀的X-Ray性能分析工具进行准确测量的人来说,tsc_freq_khz可以是一个权宜之计。

喜欢阅读这篇文章吗?嗯,我们在有趣的问题和具有挑战性的问题上茁壮成长。我们很乐意与您合作解决您的安全或软件工程挑战——请联系我们。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计