rdtsc乱序执行的解决方案?

Vai*_*xit 2 c++ gcc cpu-architecture memory-barriers rdtsc

我正在尝试将clock_gettime(CLOCK_REALTIME, &ts) 替换为rdtsc,以CPU 周期而不是服务器时间来衡量代码执行时间。基准测试代码的执行时间对于软件至关重要。我尝试在独立核心上的 x86_64 3.20GHz ubuntu 机器上运行代码并得到以下数字:

情况 1:时钟获取时间: 24 纳秒

void gettime(Timespec &ts) {
        clock_gettime(CLOCK_REALTIME, &ts);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

Pet*_*des 5

您想要lfence;rdtsc启动时钟rdtscp;lfence停止时钟,因此障碍物位于定时间隔之外。

(或者有时您想lfence;rdtsc;lfence启动时钟,以获得额外的可重复性,但代价是更多的开销。)

MFENCE 是错误的指令;它不能保证序列化指令流(但实际上它在具有最新微代码的 Skylake 上可以序列化,以修复错误)。LFENCE 序列化指令流,无需等待存储缓冲区变空,只用于 ROB。这在 Intel 上始终如此,但在 AMD 上仅在启用 Spectre 缓解的情况下才产生lfenceNOP。(我猜 AMD 不会movntdqa对 WC 内存中的加载进行重新排序,因此lfence作为内存屏障毫无意义,作为针对推测执行或 RDTSC 的执行屏障有用。)

另请参阅如何从 C++ 获取 x86_64 中的 CPU 周期计数?其中有一个关于序列化的部分rdtsc。而且,您不需要为此使用内联汇编;使用__rdtsc()_mm_lfence(). (但与微基准测试一样,检查编译器的 asm 输出以确保它执行您想要的操作并不是一个坏主意。)


你无法避免开销,与几条指令的成本相比,它总是很大。

还有clflush 通过 C 函数使缓存行无效,作为减去测量开销的示例。

但还要注意,通常将测试代码放入循环中更有用,因为结果准备好之前的执行延迟比等待指令实际从 ROB 中退出更有意义。请参阅NASM 中的 RDTSCP 始终返回相同的值(对单个指令进行计时),以获取测量单个 insn 吞吐量/延迟的示例(在 asm 中)。