全面获取 TSC 频率:提升性能分析与基准测试精度
TSC Frequency For All: Better Profiling and Benchmarking
Artem Dinaburg
October 03, 2019
containers, linux, research-practice
你是否曾尝试使用 LLVM 的 X-Ray 性能分析工具生成火焰图,却遇到如下晦涩错误?
更糟糕的是,是否曾遇到分析应用程序中所有函数运行时,发现 20 分钟的运行时长中函数运行时间总和仅约 15 分钟?那丢失的 5 分钟去哪了?
我们遇到过这两种情况,因此开发了名为tsc_freq_khz
的 Linux 内核模块作为解决方案。我们将快速但深入地探讨 x86 计时器来解释这个问题,但首先让我们了解核心方案。
核心方案
tsc_freq_khz
模块增强了虚拟化环境中性能分析和基准测试工具(如 X-Ray)的性能。不再出现“无法确定 CPU 频率”错误!该模块还提高了这些工具在新款 Intel 处理器上的准确性。
X-Ray 使用 x86 时间戳计数器(TSC)——一种低延迟单调递增时钟——来测量事件持续时间,并假设 TSC 频率等同于最大时钟速度。这个假设在新款 CPU 上是错误的,会导致时间测量不准确。tsc_freq_khz
模块提供了读取实际 TSC 频率的方法。分析数据中不再丢失分钟数!
该模块通过 sysfs 将 Linux 内核内部 x86 时间戳计数器频率值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 以滴答数测量时间,这些滴答在不同处理器之间不可比较,因此基本上没有意义。对于性能分析和基准测试,我们需要以可比较的单位(如纳秒)测量时间。
从滴答到纳秒
那么一个滴答有多少纳秒?将滴答持续时间转换为纳秒的基本公式很简单:
不幸的是,确定 TSC 频率很困难,在某些情况下(例如基于云或其他虚拟化环境)甚至不可能。直到现在。
核心问题是 Linux 内核没有提供应用程序了解 TSC 频率的方法,尽管 Linux perf 实用程序确实尝试通过 Intel PT 计算 TSC 频率。不直接暴露该值有合理理由,因为 TSC 频率相当晦涩,并且在某些情况下该值完全无意义。直到最近,处理器的最大时钟速度也是 TSC 频率的准确近似值。
然而,这不再成立。使用最大时钟速度作为 TSC 频率会在新款 Intel CPU 上给出错误结果。这是性能分析中丢失分钟数的原因。
此外,在基于云或其他虚拟化环境中无法通过/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
访问最大时钟速度。这是“无法确定 CPU 频率”错误的原因。
cpuinfo_max_freq
由用于频率调节的 cpufreq 驱动程序填充——即根据节能设置使 CPU 运行更快或更慢。自然,虚拟化环境中通常不允许频率调节,因为每个物理 CPU 与多个虚拟租户共享。因此,该值不存在。
Google 的提示
X-Ray 中的时间戳测量代码引用了一个神秘的 sysfs 条目/sys/devices/system/cpu/cpu0/tsc_freq_khz
,这听起来正好提供了我们需要的:以千赫兹为单位的 TSC 频率。不幸的是,Linux 内核源代码中完全没有对该文件的引用。这是怎么回事?
更多搜索在 Abseil 源代码中揭示了以下提示:
中奖了!注释告诉我们:
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 上的构建说明,通过将模块插入内核来测试:
现在应填充/sys/devices/system/cpu/cpu0/tsc_freq_khz
文件。(您系统上的值会不同。)
警告:请不要在真实生产系统中使用此代码。虽然它不应该引起问题,但它确实做了一些假设,例如 CPU0 存在。也没有健全性检查来警告 TSC 值是否不合理或不可靠。
结论
像这样看似简单的问题可能让我们深入一个迷人的兔子洞。随着时间的推移,Google 的内部补丁可能会进入 Linux 内核,或者内核开发人员可能会创建自己的官方补丁来将 TSC 频率导出到用户空间。同时,对于希望使用 LLVM 优秀的 X-Ray 性能分析工具进行精确测量的用户,tsc_freq_khz
可以作为一个临时解决方案。
喜欢阅读这篇文章吗?我们在有趣的问题和具有挑战性的难题上茁壮成长。我们很乐意与您合作解决您的安全或软件工程挑战——请联系我们。
分享此文章:Twitter | LinkedIn | GitHub | Mastodon | Hacker News
页面内容导航:
时间戳的问题
从滴答到纳秒
Google 的提示
全民 TSC 频率!
结论
近期文章:
使用 Deptective 调查您的依赖项
做好准备,Buttercup,AIxCC 评分轮正在进行中!
使您的智能合约超越私钥风险
Go 解析器中意外的安全隐患
我们审查首批 DKLs23 库的收获
© 2025 Trail of Bits.
使用 Hugo 和 Mainroad 主题生成。更多精彩内容 请关注我的个人公众号 公众号(办公 AI 智能小助手)公众号二维码

评论