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

时间:2017-01-22 03:13:10

标签: c++ linux timestamp x86-64

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);

2 个答案:

答案 0 :(得分:5)

使用rdtsc执行合理测量所需的是序列化指令。

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

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

  

MFENCE不会序列化指令流。

几乎序列化的指令将在以前的商店不需要完成的任何测量中执行​​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将重新排序两个{{ 1}}即使一个人正好接着另一个人。

想象一下,我们有一个rdtsc 完全,如rdtsc2但写rdtsc 1

执行

ecx:ebx

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

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

答案 1 :(得分:3)

rdtsc 之前,

mfence 是强制在CPU中进行序列化的。

通常你会在那里找到 cpuid (这也是序列化指令)。

英特尔手册中有关使用 rdtsc 的报价会更清楚

  

从Intel Pentium处理器开始,大多数Intel CPU都支持   无序执行代码。目的是优化   由于不同的指令延迟而受到处罚。不幸   此功能不保证时间顺序   单个编译的C指令将遵循该序列   指令自己写在源C文件中。当我们打电话   在RDTSC指令中,我们假装该指令将是   正好在代码开始和结束时执行   测量(即,我们不想测量执行的编译代码   在RDTSC之外调用或在调用之间执行   他们自己)。   解决方案是之前调用序列化指令   称之为RDTSC。序列化指令是指令   这会强制CPU完成C的每个前面的指令   继续执行程序之前的代码。通过这样做,我们保证   只会测量正在测量的代码   在RDTSC调用之间,并且不会执行该代码的任何部分   在电话之外。

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

提示 - 尽可能使用rdtscp。

  

根据我的测试,找不到cpu重新排序。

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