我想获得特定点的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;它选择 RDX或RAX。只有在32位模式下,它才会选择您想要的EDX:EAX输出。参见如何从c++中获取x86_64中的CPU周期计数?)
问题是它总是返回一个递增的数字(每次运行)。就好像它指的是绝对时间。
我是否错误地使用了函数?
只要您的线程停留在同一个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;
请注意,如果需要与周围代码隔离的非常精确的测量,则需要在调用rdtsc
(或使用仅在较新的处理器上支持的rdtscp
)之前进行序列化,即停止管道。可以在每个特权级别上使用的序列化指令是cpuid
。
回答评论中进一步的问题:
当你打开计算机时,TSC从0开始(BIOS将所有cpu上的所有计数器重置为相同的值,尽管几年前的一些BIOS不可靠)。
因此,从程序的角度来看,计数器开始于"过去的某个未知时间",并且它总是随着CPU看到的每个时钟滴答声而增加。因此,如果您现在和以后的任何时间在不同的进程中执行返回该计数器的指令,它将返回一个更大的值(除非CPU在此期间被挂起或关闭)。同一个程序的不同运行得到更大的数字,因为计数器一直在增长。总是这样。
现在,clock_gettime(CLOCK_PROCESS_CPUTIME_ID)
是另一回事。这是操作系统分配给进程的CPU时间。当你的过程开始时,它从0开始。一个新的过程也从零开始。因此,两个相互运行的进程将得到非常相似或相同的数字,而不是不断增长的数字。
clock_gettime(CLOCK_MONOTONIC_RAW)
更接近RDTSC的工作方式(并且在一些较旧的系统上使用它实现)。它返回一个不断增加的值。现在,这是典型的HPET。然而,这实际上是时间,而不是刻度。如果您的计算机进入低功耗状态(例如以正常频率的1/2运行),它将仍以相同的速度前进。
关于TSC有很多令人困惑和/或错误的信息,所以我想我应该试着澄清一些。
当英特尔第一次引入TSC(在原始的奔腾cpu中)时,明确记录了计算周期(而不是时间)。然而,当时的cpu大多以固定的频率运行,所以有些人忽略了记录的行为,而是用它来测量时间(最明显的是Linux内核开发人员)。他们的代码在后来的cpu中不以固定频率运行(由于电源管理等原因)。大约在那个时候,其他CPU制造商(AMD, Cyrix, Transmeta等)感到困惑,有些实现了TSC来测量周期,有些实现了TSC来测量时间,有些使其可配置(通过。MSR)。
然后"多芯片"系统在服务器中变得更加普遍;后来又出现了多核。这导致不同内核上的TSC值之间存在微小差异(由于不同的启动时间);但更重要的是,它也导致了不同cpu上TSC值的主要差异,这是由于cpu以不同的速度运行(由于电源管理和/或其他因素)。
从一开始就试图使用错误的人(用它来测量时间而不是周期的人)抱怨很多,并最终说服CPU制造商标准化,使TSC测量时间而不是周期。
当然,这是一个混乱-例如,如果你支持所有80x86 cpu,它需要大量的代码来确定TSC实际测量什么;不同的电源管理技术(包括SpeedStep和睡眠状态)可能会在不同的cpu上以不同的方式影响TSC;所以AMD在CPUID中引入了一个"TSC不变"标志,告诉操作系统TSC可以用来正确地测量时间。
最近所有的英特尔和AMD cpu都是这样的——TSC计算时间,根本不测量周期。这意味着如果您想测量周期,就必须使用(特定于模型的)性能监视计数器。不幸的是,性能监视计数器更加混乱(由于它们特定于模型的性质和复杂的配置)。
已经给出了很好的答案,并且Damon已经在他的回答中以某种方式提到了这一点,但是我将从RDTSC的实际x86手册(卷2,4 -301)条目中添加这一点:
将处理器时间戳计数器(64位MSR)的当前值加载到EDX:EAX寄存器中。EDX寄存器加载了MSR的高阶32位,EAX寄存器加载了低阶32位。(在支持Intel 64架构的处理器上,RAX和RDX的高阶32位被清除。)
处理器单调地增加每个时钟周期的时间戳计数器MSR,并在处理器复位时将其重置为0。请参阅Intel®64和IA-32架构软件开发人员手册,卷3B第17章中的"时间戳计数器",以了解时间戳计数器行为的具体细节。