在x86_64平台上需要rdtsc吗?

unsigned int lo = 0; unsigned int hi = 0; __asm__ __volatile__ ( "mfence;rdtsc" : "=a"(lo), "=d"(hi) : : "memory" ); 

在上面的代码中,是否有必要?

根据我的testing,没有findcpu reorder。

testing代码片段如下。

 inline uint64_t clock_cycles() { unsigned int lo = 0; unsigned int hi = 0; __asm__ __volatile__ ( "rdtsc" : "=a"(lo), "=d"(hi) ); return ((uint64_t)hi << 32) | lo; } unsigned t1 = clock_cycles(); unsigned t2 = clock_cycles(); assert(t2 > t1); 

你需要用rdtsc进行合理的测量是一个序列化指令。

众所周知,很多人 rdtsc 之前使用cpuid
rdtsc需要从上面下面序列化(阅读:所有的指令必须退休之前,必须在测试代码开始之前退出)。

不幸的是,第二个条件经常被忽略,因为cpuid是这个任务的一个非常糟糕的选择(它破坏了rdtsc的输出)。
在寻找替代品的时候,人们会认为名称上有“栅栏”的指示会起作用,但这也是不正确的。 从英特尔直接:

MFENCE不会序列化指令流。

几乎序列化的指令,并且在任何先前的商店不需要完成的测量中都可以lfence

简单地说,在任何先前的指令在本地完成之前,确保没有新的指令开始。 看到我的这个答案对地方的更详细的解释 。
它也不会像mfence那样耗尽存储缓冲区,并且不像cpuid那样破坏寄存器。

因此, lfence / rdtsc / lfence是比mfence / rdtsc更好的指令序列,其中mfence几乎是无用的,除非您明确地希望在测试开始/结束之前完成以前的存储(而不是在执行rdstc之前)。


如果你的测试检测重新排序是assert(t2 > t1)那么我相信你什么都不会测试。
退出return和通话可能会或可能不会阻止CPU看到第二个rdtsc重新排序,这是不可能的(尽管可能!),即使一个接一个地重新排列两个rdtsc ,也是不可能的。

想象一下,我们有一个完全rdtsc但写入ecx:ebx 1

执行

 rdtsc rdtsc2 

很有可能是ecx:ebx > edx:eax因为CPU 没有理由rdtsc2之前执行rdtsc
重新排序并不意味着随机排序,这意味着如果当前的那个不能被执行 ,那么寻找其他的指令。
但是rdtsc不依赖于任何先前的指令,所以当被OoO内核遇到时不太可能被延迟。
然而,特殊的内部微观建筑细节可能会使我的论文无效,因此我之前的发言中可能会出现这个词。


1我们不需要这个改变的指令:寄存器重命名会做,但如果你不熟悉它,这将有所帮助。

mfencerdtsc之前是强制在CPU中进行序列化的。

通常你会发现cpuid (这也是序列化指令)。

从英特尔手册引用关于使用rdtsc将使它更清晰

从英特尔奔腾处理器开始,大多数英特尔CPU支持代码的无序执行。 目的是为了优化由于不同的指令延迟造成的惩罚。 不幸的是,这个特性并不能保证单个编译的C指令的时间顺序将遵循写在源C文件中的指令本身的顺序。 当我们调用RDTSC指令时,我们假设指令将在被测代码的开始和结束时执行(即,我们不想测量在RDTSC调用之外执行或在自称)。 解决的办法是在调用RDTSC之前调用序列化指令。 序列化指令是在继续执行程序之前强制CPU完成每个C代码的指令的指令。 通过这样做,我们保证只有在测量的代码将在RDTSC调用之间执行,并且该代码的任何部分都不会在调用之外执行。

TL; DR版本 – 在rdtsc之前没有序列化指令,你不知道该指令何时开始执行测量可能不正确。

提示 – 尽可能使用rdtscp。

根据我的测试,没有找到cpu reorder。

仍然不能保证它可能发生 – 这就是为什么原始代码有"memory"来指示可能的内存clobber阻止编译器重新排序它。