在x86_64平台上是否需要rdtsc的mfence?

Scy*_*Scy 2 c++ linux timestamp x86-64

unsigned int lo = 0;
unsigned int hi = 0;
__asm__ __volatile__ (
    "mfence;rdtsc" : "=a"(lo), "=d"(hi) : : "memory"
);
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)

Mar*_*oom 5

rdtsc使用序列化指令执行合理测量所需的内容.

众所周知,很多人cpuid 以前都在 使用rdtsc.
rdtsc需要从上方下方序列化(读取:必须退出之前的所有指令,并且必须在测试代码开始之前退出).

不幸的是,第二个条件经常被忽略,因为cpuid这个任务是一个非常糟糕的选择(它破坏了输出rdtsc).
在寻找替代方案时,人们会认为在他们的名字中有"围栏"的说明会有用,但这也是不真实的.直接来自英特尔:

MFENCE不会序列化指令流.

几乎序列化的指令,将在以前的商店不需要完成的任何测量中执行lfence.

简单地说,lfence确保在任何先前指令在本地完成之前没有新指令开始.请参阅我的这个答案,以获得有关当地情况的更详细解释.
它也不会像存在那样耗尽存储缓冲区mfence,也不会像寄存器那样破坏寄存器cpuid.

因此,除非您明确希望在测试开始/结束之前完成之前的存储(但不是之前执行!),否则lfence / rdtsc / lfence指令序列更好mfence / rdtsc,mfence除非您明确无用rdstc.


如果您检测重新排序的测试是assert(t2 > t1)我认为您将测试什么.
抛出return可能会或可能不会阻止CPU rdtsc及时查看重新排序的调用,不太可能(尽管可能!)CPU将重新排序两个,rdtsc即使一个正好在另一个之后.

试想一下,我们有一个rdtsc2精确地喜欢rdtsc,但写入ecx:ebx1.

执行

rdtsc
rdtsc2
Run Code Online (Sandbox Code Playgroud)

很可能是ecx:ebx > edx:eax因为CPU 之前没有理由执行. 重新排序并不意味着随机排序,它意味着如果当前的指令无法执行,则查找其他指令. 但是它不依赖于任何先前的指令,因此当OoO核心遇到它时,它不太可能被延迟. 然而,特殊的内部微观建筑细节可能会使我的论文无效,因此在我之前的陈述中可能是这个词.rdtsc2rdtsc

rdtsc


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