标准::计时::时钟、硬件时钟和周期计数

std::chrono::clock, hardware clock and cycle count

本文关键字:时钟 周期 计时 标准 硬件      更新时间:2023-10-16

std::chrono提供了几个时钟来测量时间。同时,我想CPU评估时间的唯一方法是计算周期。

问题 1:除了计算周期之外,CPU 或 GPU 是否有任何其他方法来评估时间?

如果是这样的话,因为计算机计数周期的方式永远不会像原子钟那样精确,这意味着计算机的"秒"(period = std::ratio<1>)实际上可能比实际秒短或大,导致计算机时钟和GPS之间的时间测量长期存在差异。

问题2:这是对的吗?

某些硬件具有不同的频率(例如空闲模式和涡轮增压模式)。在这种情况下,这意味着周期数将在一秒钟内变化。

问题 3:CPU 和 GPU 测量的"周期计数"是否因硬件频率而异?如果是,那么std::chrono如何处理呢?如果不是,周期对应于什么(比如什么是"基本"时间)?有没有办法在编译时访问转换?有没有办法在运行时访问转换?

计算周期,是的,但是周期是什么

在现代 x86 上,内核使用的时间源(内部以及用于clock_gettime和其他系统调用)通常是计时器中断或偶尔读取的硬件计时器(例如 HPET)。(我实际上不知道细节;当我写这篇文章时,我认为一切都是基于rdtsc但我认为这是不正确的。 如果网络可用,通常使用 NTP 来校正比例因子以保持系统时间正确。

细粒度定时来自固定频率计数器,该计数器计算"参考周期",而不管涡轮增压、省电或时钟停止空闲。 (这是您从 C/C++ 中rdtsc__rdtsc()获得的计数器,请参阅此内容以获取更多详细信息,例如,在较旧的 CPU 上,它实际上确实计算了内核时钟周期,并且在睡眠状态下没有滴答作响,因此对于挂钟时间不太有用。

正常的std::chrono实现将使用操作系统提供的功能,如 POSIXclock_gettime

在Linux上,这可以纯粹在用户空间中运行。 VDSO 页面中的代码 + 数据由内核映射到每个进程的地址空间。 数据包括由计时器中断更新的粗略时间戳(我认为CLOCK_REALTIME_COARSE或直接返回这些时间戳CLOCK_MONOTONIC_COARSE),以及使用 TSC 从系统时钟的最后一个时钟周期获得细粒度偏移的偏移量和比例因子。 低开销时间源很好。 避免用户>内核>用户的往返有很大帮助,在启用 Meltdown + Spectre 缓解的情况下更是如此,因为这会使真正的系统调用更加昂贵。

分析不受内存限制的紧密循环可能需要使用实际的内核时钟周期,因此它对当前内核的实际速度不敏感。 (并且不必担心将CPU提升到最大涡轮增压等) 例如,使用perf stat ./a.outperf record ./a.out。 例如,x86的MOV真的可以"免费"吗?为什么我根本无法重现这个?


某些系统没有/没有内置在CPU中的挂钟等效计数器,因此您只有一个粗略的时间可用,在计时器中断时在RAM中更新。 或者时间查询函数会从单独的芯片读取时间,可能具有高精度。

(系统调用 + 硬件 I/O = 更高的开销,这是 x86 的rdtsc指令从分析事物转变为时钟源事物的部分原因。

所有这些时钟频率最终都来自mobo上的晶体振荡器。但是,正如@Tony指出的那样,可以调整从周期计数推断时间的比例因子,以保持时钟与原子时间同步,通常使用网络时间协议(NTP)。

问题 1:除了计算周期之外,CPU 或 GPU 还有其他方法来评估时间吗?

不同的硬件可能提供不同的设施。 例如,x86 PC 采用了多种硬件设施进行计时:在过去十年左右的时间里,x86 CPU 的时间戳计数器以其处理频率或最近的一些固定频率("恒定速率"又名"不变"TSC)运行;可能有一个高精度事件定时器,再往前追溯,还有可编程中断定时器(https://en.wikipedia.org/wiki/Programmable_interval_timer)。

如果是这样的话,因为计算机计算周期的方式永远不会像原子钟那样精确,这意味着计算机的"秒"(周期= std::ratio<1>)实际上可能比实际秒短或大,导致计算机时钟和GPS之间的时间测量长期存在差异。

是的,没有原子钟的计算机(它们现在在芯片上可用)不会像原子钟那样精确。 也就是说,网络时间协议等服务允许您在一堆计算机之间保持更紧密的一致性。 它有时通过使用每秒脉冲(PPS)技术来辅助。 更现代和准确的变体包括精确时间协议 (PTP)(通常可以在 LAN 上实现亚微秒级精度)。

问题 3:CPU 和 GPU 测量的"周期计数"是否因硬件频率而异?

这要看情况。 对于TSC,较新的"恒定速率"TSC实现不会有所不同,其他实现会有所不同。

如果是,那么 std::chrono 如何处理它?

我希望大多数实现调用操作系统提供的时间服务,因为操作系统往往对硬件有最好的了解和访问。 需要考虑很多因素 - 例如,TSC读数是否在内核之间同步,如果PC进入某种睡眠模式会发生什么,TSC采样周围需要哪种方式的内存围栏......

如果不是,周期对应于什么(比如什么是"基本"时间)?

对于英特尔 CPU,请参阅此答案。

有没有办法在编译时访问转换?有没有办法在运行时访问转换?

std::chrono::duration::count公开了使用的任何时间源的原始刻度计数,您可以duration_cast到其他时间单位(例如秒)。 C++20预计将引入更多设施,如clock_cast。 AFAIK,没有可用的constexpr转换:如果程序最终可能在与编译它的机器不同的 TSC 速率的机器上运行,似乎也很可疑。