unsigned int lo = 0;
unsigned int hi = 0;
__asm__ __volatile__ (
"mfence;rdtsc" : "=a"(lo), "=d"(hi) : : "memory"
);
上面的代码中的
mfence
,有必要吗?根据我的测试,找不到cpu重新排序。
测试代码的片段包括在下面。
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
执行明智的测量所需的是一条序列化指令。
众所周知,很多人在cpuid
之前使用rdtsc
。rdtsc
需要从上到下进行序列化(阅读:必须退休之前的所有指令,并且在测试代码开始之前必须退休)。
不幸的是,第二个条件经常被忽略,因为cpuid
对于此任务而言是一个非常糟糕的选择(它掩盖了rdtsc
的输出)。
当人们寻找替代品时,人们会认为名称中带有“栅栏”的指令会起作用,但这并非事实。直接来自英特尔:
lfence
是一条几乎可以序列化的指令,可以在以前的存储不需要完成的任何测量中使用。
简而言之,lfence
确保在本地任何先前的指令完成之前,没有新的指令开始。参见this answer of mine for a more detailed explanation on locality。
它也不会像mfence
那样耗尽存储缓冲区,也不会像cpuid
那样浪费寄存器。
因此,lfence / rdtsc / lfence
是比mfence / rdtsc
更好的指令序列,其中mfence
几乎没有用,除非您明确希望在测试开始/结束之前(而不是在执行rdstc
之前)完成先前的存储!
如果您检测到重新排序的测试是assert(t2 > t1)
,那么我相信您将不会进行任何测试。
忽略return
和可能会阻止或无法阻止CPU及时查看第二个rdtsc
的调用以进行重新排序,即使一个接一个的紧接着另一个,CPU也不太可能(尽管有可能!)对两个rdtsc
进行重新排序。
想象一下,我们有一个rdtsc2
,它与rdtsc
完全一样,但是写入了ecx:ebx
1。
执行中
rdtsc
rdtsc2
很有可能是
ecx:ebx > edx:eax
,因为CPU没有理由在rdtsc2
之前执行rdtsc
。重新排序并不意味着随机排序,而是意味着如果当前指令无法执行,则寻找其他指令。
但是
rdtsc
不依赖任何先前的指令,因此当OoO内核遇到它时,它不太可能被延迟。但是,内部微体系结构的特殊细节可能会使我的论文无效,因此我以前的陈述中可能使用这个词。
1我们不需要此更改的指令:可以重命名寄存器,但是如果您不熟悉它,则将有所帮助。
关于c++ - 在x86_64平台上是否需要rdtsc的功能?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/41786929/