rdtsc乱序执行的解决方案?

时间:2019-02-14 12:43:34

标签: c++ gcc cpu-architecture memory-barriers rdtsc

我正在尝试用rdtsc替换clock_gettime(CLOCK_REALTIME,&ts),以便根据cpu周期而不是服务器时间来测试代码执行时间。基准标记代码的执行时间对于软件至关重要。我尝试在隔离的内核上的x86_64 3.20GHz ubuntu机器上运行代码,并得到以下数字:

案例1:时钟获取时间: 24纳秒

void gettime(Timespec &ts) {
        clock_gettime(CLOCK_REALTIME, &ts);
}

情况2:rdtsc(无mfence和编译器障碍): 10 ns

void rdtsc(uint64_t& tsc) {
        unsigned int lo,hi;
        __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
        tsc = ((uint64_t)hi << 32) | lo;
}

情况3:rdtsc(具有mfence和编译器屏障): 30 ns

void rdtsc(uint64_t& tsc) {
        unsigned int lo,hi;
        __asm__ __volatile__ ("mfence;rdtsc" : "=a" (lo), "=d" (hi) :: "memory");
        tsc = ((uint64_t)hi << 32) | lo;
}

这里的问题是我知道rdtsc是一个非序列化的调用,可以由CPU重新排序,另一个选择是rdtscp,它是一个序列化调用,但是rdtscp调用之后的指令可以在rdtscp调用之前重新排序。使用内存屏障会增加执行时间。

  • 对延迟敏感代码进行基准测试的最优化,最佳方法是什么?
  • 总有没有要优化我提到的案例?

1 个答案:

答案 0 :(得分:2)

您希望lfence;rdtsc 开始时钟,而rdtscp;lfence停止时钟,因此障碍不在定时间隔内。

(或者有时您希望lfence;rdtsc;lfence开始计时,以增加可重复性,但要付出更多的开销。)

MFENCE是错误的指令;不能保证对指令流进行序列化(但实际上,它在Skylake上使用最新的微代码来修复错误)。 LFENCE仅对ROB序列化指令流,而无需等待存储缓冲区为空。在Intel上总是如此,但在AMD上只有启用了Spectre缓解功能,这使得lfence不仅仅是NOP。 (我猜想AMD不会从WC内存中重新排序movntdqa的加载,因此lfence作为内存障碍毫无意义,并且用作防止推测执行的执行障碍,或用于RDTSC。)

另请参见Get CPU cycle count?,其中包含有关序列化rdtsc的部分。但是,您也不需要内联汇编。使用__rdtsc()_mm_lfence()。 (但与微基准测试一样,检查编译器的asm输出以确保其符合您的要求不是一个坏主意。)


您不可避免地要避免开销,这与几个指令的费用相比总是很重要的。

也以clflush to invalidate cache line via C function为例,减去了测量开销。

但也请注意,通常将代码置于循环中是更有用的,因为在结果准备好之前的执行等待时间比等待指令实际从ROB退出更有意义。请参见RDTSCP in NASM always returns the same value,以获取一个示例insn的吞吐量/延迟示例(以asm格式)。