使用RDTSC获取CPU周期 – 为什么RDTSC的值总是增加?

我想在特定的点上获得CPU周期。 我在这个时候使用这个函数:

static __inline__ unsigned long long rdtsc(void) { unsigned long long int x; __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x)); return x; } 

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

我使用function不正确吗?

只要你的线程停留在同一个CPU内核上,RDTSC指令就会一直返回一个数字,直到它绕过。 对于2GHz的CPU来说,这是在292年后发生的,所以这不是一个真正的问题。 你可能不会看到它发生。 如果你希望活得那么久,确保你的电脑每隔50年就要重启一次。

RDTSC的问题在于,您不能保证它在一个老式多核CPU的所有内核上的相同时间点上启动,并且不能保证它在一个老式多CPU板上的所有CPU上的相同时间点开始。
现代系统通常不存在这样的问题,但是通过设置线程的相似性,也可以在较老的系统上解决问题,所以它只能在一个CPU上运行。 这对于应用程序性能并不好,所以一般不应该这样做,但是对于测量蜱而言,这样做很好。

(另一个“问题”是很多人使用RDTSC来测量时间,这不是它所做的,但是你写了你想要CPU周期,所以这很好,如果你使用RDTSC来测量时间,你可能会有惊喜省电或者超级增压(hyperboost),或者任何众多的频率变化技术被称为英寸。在实际的时间里, clock_gettime系统调用在Linux下是惊人的。

我只是在asm语句里写rdtsc ,对我来说工作得很好,比一些十六进制的代码更可读。 假设它是正确的十六进制代码(既然它既不崩溃,并返回一个不断增加的数字,似乎是这样),你的代码是好的。

如果你想测量一段代码所需的滴答数量,你需要一个滴答差异 ,你只需要减去不断增加的计数器的两个值。 像uint64_t t0 = rdtsc(); ... uint64_t t1 = rdtsc() - t0; uint64_t t0 = rdtsc(); ... uint64_t t1 = rdtsc() - t0;
请注意,如果需要从周围代码中隔离出非常准确的测量结果,则需要在调用rdtsc (或使用仅在较新处理器上支持的rdtscp之前序列化,即暂停流水线。 可以在每个特权级别使用的一个序列化指令是cpuid

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

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

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

现在, clock_gettime(CLOCK_PROCESS_CPUTIME_ID)是另外一回事。 这是操作系统为进程提供的CPU时间。 当你的过程开始时,它从零开始。 一个新的过程也从零开始。 因此,两个相互依次运行的进程会得到非常相似或相同的数字,而不是不断增长的进程​​。

clock_gettime(CLOCK_MONOTONIC_RAW)更接近RDTSC的工作方式(在一些较旧的系统上实现)。 它返回一个增加的值。 现在,这通常是HPET。 但是,这是真的时间 ,而不是蜱虫 。 如果您的电脑进入低功耗状态(例如以正常频率运行),它仍然会以同样的速度前进。

在那里有很多关于TSC的混淆和/或错误的信息,所以我想我会尝试清除一些。

当英特尔首次推出TSC(在原有的奔腾CPU中)时,它清楚地记录了周期(而不是时间)。 然而,当时的CPU大多运行在一个固定的频率上,所以有些人忽略了记录的行为,并用它来衡量时间(最值得注意的是,Linux内核开发者)。 他们的代码打破了以后的CPU不能运行在一个固定的频率(由于电源管理等)。 大约在那个时候,其他的CPU制造商(AMD,Cyrix,Transmeta等)都感到困惑,有些实现了TSC来测量周期,有些实现了测量时间,有些则通过MSR进行配置。

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

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

当然这是一团糟 – 例如,如果支持所有的80×86 CPU,只需要很多代码来确定TSC的实际措施; 不同的电源管理技术(包括像SpeedStep这样的东西,还有睡眠状态等)可能在不同的CPU上以不同的方式影响TSC; 所以AMD在CPUID中引入了一个“TSC不变”标志,告诉操作系统TSC可以用来正确测量时间。

所有最近的英特尔和AMD处理器都已经有一段时间了 – TSC计算时间,根本不测量周期。 这意味着如果你想测量周期你必须使用(模型特定)的性能监测计数器。 不幸的是,性能监控计数器是一个更糟糕的混乱(由于他们的模型特定的性质和令人费解的配置)。

已经有了很好的答案,Damon在他的回答中已经提到了这一点,但是我将从RDTSC的实际x86手册(第2卷,4-301)

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

处理器在每个时钟周期单调递增时间戳计数器MSR,并在处理器复位时将其重置为0。 有关时间戳计数器行为的具体细节,请参阅英特尔®64和IA-32架构软件开发人员手册,卷3B第17章中的“时间戳计数器”。