20

我想在特定点获得 CPU 周期。我当时使用这个功能:

static __inline__ unsigned long long rdtsc(void)
{
    unsigned long long int x;
    __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
    // broken for 64-bit builds; don't copy this code
    return x;
}

(编者注:"=A"x86-64 是错误的;它选择RDXRAX。只有在 32 位模式下,它才会选择您想要的 EDX:EAX 输出。请参阅如何从 C++ 获取 x86_64 中的 CPU 周期计数?。)

问题是它总是返回一个增加的数字(在每次运行中)。就好像它指的是绝对时间。

我是否错误地使用了这些功能?

4

3 回答 3

32

只要您的线程保持在同一个 CPU 内核上,RDTSC 指令就会不断返回一个递增的数字,直到它回绕。对于 2GHz CPU,这发生在 292 年后,所以这不是一个真正的问题。你可能不会看到它发生。如果您希望活得那么久,请确保您的计算机重新启动,例如,每 50 年。

RDTSC 的问题是你不能保证它在老多核 CPU 的所有内核上在同一时间点启动,也不能保证它在老多核 CPU 板上的所有 CPU 上在同一时间点启动.
现代系统通常不存在此类问题,但也可以通过设置线程的亲和性使其仅在一个 CPU 上运行,从而在旧系统上解决该问题。这对应用程序性能不利,因此通常不应该这样做,但对于测量滴答声,这很好。

(另一个“问题”是很多人使用RDTSC来测量时间,这不是它的作用,但是你写了你想要CPU周期,所以没关系。如果你确实使用RDTSC来测量时间,你可能会感到惊讶省电或hyperboost或任何被称为多种频率变化技术的东西。实际上,clock_gettime系统调用在Linux下非常好。)

我只想写rdtscasm语句中,这对我来说很好,并且比一些晦涩的十六进制代码更具可读性。假设它是正确的十六进制代码(并且由于它既不崩溃也不返回不断增加的数字,看起来如此),你的代码是好的。

如果你想测量一段代码的滴答数,你想要一个滴答,你只需要减去不断增加的计数器的两个值。请注意,如果需要与周围代码隔离的非常准确的测量,则需要在调用(或仅在较新的处理器上支持的使用uint64_t t0 = rdtsc(); ... uint64_t t1 = rdtsc() - t0;
)之前进行序列化,即停止管道。可以在每个特权级别使用的一个序列化指令是.rdtscrdtscpcpuid

回复评论中的进一步问题:

当您打开计算机时,TSC 从零开始(并且 BIOS 将所有 CPU 上的所有计数器重置为相同的值,尽管几年前的一些 BIOS 并不可靠)。

因此,从您的程序的角度来看,计数器在“过去某个未知时间”开始,并且它总是随着 CPU 看到的每个时钟滴答而增加。因此,如果您现在和稍后在不同的进程中执行返回该计数器的指令,它将返回一个更大的值(除非 CPU 在其间暂停或关闭)。同一程序的不同运行得到更大的数字,因为计数器不断增长。总是。

现在,clock_gettime(CLOCK_PROCESS_CPUTIME_ID)是另一回事了。这是操作系统为进程分配的 CPU 时间。当您的流程开始时,它从零开始。一个新的过程也从零开始。因此,两个相互运行的进程将获得非常相似或相同的数字,而不是永远增长的数字。

clock_gettime(CLOCK_MONOTONIC_RAW)更接近 RDTSC 的工作方式(并且在一些较旧的系统上是用它实现的)。它返回一个不断增加的值。如今,这通常是 HPET。然而,这真的是时间,而不是滴答声。如果您的计算机进入低功耗状态(例如以 1/2 正常频率运行),它仍然会以相同的速度前进。

于 2011-12-22T10:32:53.690 回答
22

那里有很多关于 TSC 的令人困惑和/或错误的信息,所以我想我会尝试澄清一些。

当 Intel 首次引入 TSC(在原始 Pentium CPU 中)时,它被明确记录为计算周期(而不是时间)。然而,当时的 CPU 大多以固定频率运行,因此有些人忽略了记录在案的行为,而是用它来测量时间(最著名的是 Linux 内核开发人员)。他们的代码闯入了后来不以固定频率运行的 CPU(由于电源管理等)。大约在那个时候,其他 CPU 制造商(AMD、Cyrix、Transmeta 等)感到困惑,一些实施 TSC 来测量周期,一些实施它来测量时间,还有一些使其可配置(通过 MSR)。

然后“多芯片”系统在服务器上变得越来越普遍;甚至后来引入了多核。这导致不同内核上的 TSC 值之间存在细微差异(由于启动时间不同);但更重要的是,由于 CPU 以不同的速度运行(由于电源管理和/或其他因素),它还导致不同 CPU 上的 TSC 值之间存在重大差异。

从一开始就试图错误地使用它的人(用它来测量时间而不是周期的人)抱怨了很多,并最终说服 CPU 制造商标准化使 TSC 测量时间而不是周期。

当然,这是一团糟——例如,如果您支持所有 80x86 CPU,则需要大量代码才能确定 TSC 实际测量的内容;不同的电源管理技术(包括 SpeedStep 之类的东西,还有睡眠状态之类的东西)可能会在不同的 CPU 上以不同的方式影响 TSC;因此 AMD 在 CPUID 中引入了“TSC 不变”标志,以告诉操作系统 TSC 可用于正确测量时间。

所有最近的 Intel 和 AMD CPU 已经有一段时间了 - TSC 计算时间,根本不测量周期。这意味着如果您想测量周期,您必须使用(特定于模型的)性能监控计数器。不幸的是,性能监控计数器更加混乱(由于它们的模型特定性质和复杂的配置)。

于 2011-12-22T15:24:26.860 回答
1

已经有了很好的答案,Damon 在他的回答中已经以某种方式提到了这一点,但我将从 RDTSC 的实际 x86 手册(第 2 卷,第 4-301 卷)条目中添加这个:

将处理器的时间戳计数器(64 位 MSR)的当前值加载到 EDX:EAX 寄存器中。EDX 寄存器加载了 MSR 的高 32 位,EAX 寄存器加载了低 32 位。(在支持 Intel 64 架构的处理器上,RAX 和 RDX 的高 32 位被清除。)

The processor monotonically increments the time-stamp counter MSR every clock cycle and resets it to 0 whenever the processor is reset. See "Time Stamp Counter" in Chapter 17 of the Intel® 64 and IA-32 Architectures Software Developer's Manual, Volume 3B, for specific details of the time stamp counter behavior.

于 2016-10-21T16:47:26.237 回答