我正在尝试用 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 调用之前重新排序。使用内存屏障会增加执行时间。
- 对延迟敏感代码进行基准测试的最优化和最佳方法是什么?
- 有没有办法优化我提到的案例?
你要lfence;rdtsc
to start时钟,以及rdtscp;lfence
停止时钟,因此障碍物位于计时间隔之外。
(或者有时你想要lfence;rdtsc;lfence
启动时钟,以获得额外的可重复性,但代价是更多的开销。)
MFENCE 是错误的指令;它不能保证序列化指令流(但实际上它在具有最新微代码的 Skylake 上可以序列化,以修复错误)。 LFENCE 序列化指令流,无需等待存储缓冲区清空,只用于 ROB。这在英特尔上始终如此,但是一项且仅启用 Spectre 缓解功能 https://stackoverflow.com/questions/51844886/is-lfence-serializing-on-amd-processors这使得lfence
不仅仅是一个NOP。 (我猜AMD不会重新排序movntdqa
从 WC 内存加载,所以lfence
作为那里的记忆屏障毫无意义,并且是only作为针对推测执行或 RDTSC 的执行屏障很有用。)
也可以看看如何从 C++ 获取 x86_64 中的 CPU 周期数? https://stackoverflow.com/questions/13772567/get-cpu-cycle-count/51907627#51907627其中有一个关于序列化的部分rdtsc
。而且,您不需要为此使用内联汇编;使用__rdtsc()
and _mm_lfence()
。 (但与微基准测试一样,检查编译器的 asm 输出以确保它执行您想要的操作并不是一个坏主意。)
你无法避免开销,与几条指令的成本相比,它总是很大。
Also clflush 通过 C 函数使缓存行无效 https://stackoverflow.com/questions/51818655/clflush-to-invalidate-cache-line-via-c-function/51830976#51830976有关减去测量开销的示例。
但还要注意,通常将测试代码放入循环中更有用,因为结果准备好之前的执行延迟比等待指令实际从 ROB 中退出更有意义。看NASM 中的 RDTSCP 始终返回相同的值(对单个指令进行计时) https://stackoverflow.com/questions/54621381/rdtscp-in-nasm-always-returns-the-same-value/54624081#54624081例如(在 asm 中)测量单个 insn 的吞吐量/延迟。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)