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之前使用rdtscrdtsc需要从上到下进行序列化(阅读:必须退休之前的所有指令,并且在测试代码开始之前必须退休)。

不幸的是,第二个条件经常被忽略,因为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/

10-11 01:51