假设我想对某个函数double a(double b, double c)
的两个相互竞争的实现进行基准测试。我已经有一个大的array <double, 1000000> vals
,我可以从中获取输入值,所以我的基准测试看起来大致如下:
//start timer here
double r;
for (int i = 0; i < 1000000; i+=2) {
r = a(vals[i], vals[i+1]);
}
//stop timer here
现在,一个聪明的编译器可以意识到我只能使用最后一次迭代的结果,并简单地杀死其余的,留给我double r = a(vals[999998], vals[999999])
。这当然违背了基准测试的目的。
是否有一个好的方法(如果它在多个编译器上工作则加分)来防止这种优化,同时保持所有其他优化?
(我已经看到关于插入空asm
块的其他线程,但我担心这可能会阻止内联或重新排序。我也不是特别喜欢在每次迭代期间添加结果sum += r;
的想法,因为这是不应该包含在结果计时中的额外工作。为了这个问题的目的,如果我们能专注于其他替代解决方案,那就太好了,尽管对于对此感兴趣的人来说,在评论中有一个热烈的讨论,共识是+=
在许多情况下是最合适的方法。)
将a
放在单独的编译单元中,并且不使用LTO(链接时间优化)。这种方式:
- 循环总是相同的(由于基于
a
的优化而没有差异) - 函数调用的开销总是相同的
- 要测量纯粹的开销并有一个基线来比较实现,只需对
a
的空版本进行基准测试
请注意,编译器不能假设对a
的调用没有副作用,因此它不能优化循环并将其替换为最后一次调用。
一种完全不同的方法可以使用RDTSC,它是CPU核心中的硬件寄存器,用于测量时钟周期。它有时对微基准测试很有用,但正确理解结果并非微不足道。例如,查看这个并搜索/搜索SO以获取有关rdtsc的更多信息。