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