2

我正在尝试用 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 调用之前重新排序。使用内存屏障会增加执行时间。

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

1 回答 1

3

您想lfence;rdtsc启动时钟rdtscp;lfence停止时钟,因此障碍在时间间隔之外。

(或者有时你想lfence;rdtsc;lfence启动时钟,以增加开销为代价获得额外的可重复性。)

MFENCE 是错误的指令;不能保证序列化指令流(但实际上它在 Skylake 上使用最新的微码来修复错误)。LFENCE 序列化指令流而不等待存储缓冲区清空,只为 ROB。在英特尔上总是如此,但在 AMD 上,只有启用了 Spectre 缓解,这lfence不仅仅是一个 NOP。(我猜 AMD 不会重新排序movntdqa来自 WC 内存的负载,因此lfence作为内存屏障没有意义,并且可用作针对推测执行或 RDTSC 的执行屏障。)

另请参阅获取 CPU 周期计数?其中有一个关于序列化的部分rdtsc。而且,你不需要内联汇编;使用__rdtsc()_mm_lfence()。(但像往常一样使用微基准,检查编译器的 asm 输出以确保它符合您的要求并不是一个坏主意。)


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

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

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

于 2019-02-14T14:11:26.910 回答