周期计数本身在程序时序上是否可靠?

Is cycle count itself reliable on program timing?

我目前正在尝试开发一个判断系统,它不仅可以测量时间和内存使用情况,还可以测量更深层次的信息,例如缓存未命中等,我认为硬件计数器(使用 perf)非常适合它。

但是对于计时部分, 不知道单纯用周期数来判断执行速度靠谱吗? 希望知道这个决定的利弊。

所以你提议测量 CPU 周期,而不是秒?听起来有点道理。

对于某些微基准测试来说,这很好,并且主要排除了由于 CPU 频率变化引起的变化。 (如果你只计算 user-space 周期,如果你正在对一个不进行系统调用的循环进行微基准测试,那么由于中断而导致的延迟。然后只有中断的次要影响是可见的,即序列化管道,也许从缓存/TLB 中逐出您的一些数据。)

但内存(可能还有 L3 缓存)在 CPU 频率变化时保持恒定速度,因此缓存未命中的相对成本发生变化:相同的响应以纳秒为单位的时间是更少的核心时钟周期,因此乱序执行可以更容易地隐藏更多。 并且可用内存带宽相对于内核可以使用的带宽更高。因此硬件预取更容易跟上。

例如在 4.3GHz 时,L2 缓存中未命中但在 Skylake 服务器上的 L3 中命中的负载可能具有大约 79 个核心时钟周期的总延迟。 (https://www.7-cpu.com/cpu/Skylake_X.html - i7-7820X (Skylake X),8 核)。

在 800MHz 空闲时钟速度下,L2 缓存未命中仍然是 14 个周期(因为它在核心速度下 运行s)。但是,如果另一个核心将 L3 缓存(以及一般的非核心)保持在高时钟速度,则该往返请求的非核心部分将花费更少的核心时钟周期。

例如我们可以通过假设 L3 命中与 L2 命中的所有额外时间都花在非核心而不是核心上,并且需要固定的纳秒数来进行粗略计算。由于我们在 4.3GHz 时钟的周期中有那个时间,因此数学计算结果为 L3 在 800MHz 时命中的 14 + (79-14)*8/43 个周期 = 26 个周期,低于 79 个。

这个粗略的计算实际上与 7-cpu.com 的数字相匹配,相同 CPU 的核心频率为 3.6GHz:L3 缓存延迟 = 68 个周期。 14 + (79-14)*36/43 = 68.4.

请注意,我选择了“服务器”部分,因为不同的内核可以 运行 以不同的时钟速度运行。在像 i7-6700k 这样的“客户端”CPU 中情况并非如此。非核心(L3、互连等)可能仍然能够独立于核心而变化,例如为 GPU 保持高位。此外,服务器部分在核心之外具有更高的延迟。 (例如,禁用 Turbo 的 4GHz Skylake i7-6700k 的 L3 延迟仅为 42 个核心时钟周期,而不是 68 或 79。)

另见 why/how L3 和内存延迟影响最大可能的单核内存 带宽


当然,如果您通过允许一些热身来控制 CPU 频率,或者对于 运行 超过微不足道的时间的任务,这是没什么大不了的。

(尽管请注意,当内存受限时,Skylake 有时会降低时钟速度,不幸的是,这会进一步损害带宽,默认情况下 energy_performance_preference = balance_power,但是“balance_performance”或“性能”可以避免这种情况。)

请注意,仅计算周期 不会 消除上下文切换的成本(切换回此线程后额外的缓存未命中,并且耗尽 ROB 很糟糕)。或者与其他内核竞争内存带宽。

例如另一个线程 运行ning 在同一物理核心的另一个逻辑核心上通常会严重降低 IPC。总体吞吐量通常会增加一些,具体取决于任务,但单个每线程吞吐量会下降。

Skylake 有一个用于跟踪超线程竞争的 perf 事件:cpu_clk_thread_unhalted.one_thread_active - IIRC,当您的任务 运行ning 并且拥有全部核心时,事件计数会以大约 24MHz 的速度递增。因此,如果你看到的少于此,你就知道你有一些竞争,并且花了一些时间与 ROB 分区和交易前端周期与另一个线程。


所以有一堆效果,有没有用就看你自己了。按核心时钟周期排序结果听起来很合理,但你可能应该在结果中包含 CPU 秒(任务时钟)和平均频率 以帮助人们发现异常值/故障。