我正在尝试理解Google的DoNotOptimize()
的工作原理。
为了完整起见,这是它的定义(适用于clang和非const数据):
template <class Tp>
inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp& value) {
asm volatile("" : "+r,m"(value) : : "memory");
}
据我所了解,我们可以在代码中像这样使用它:
start_time = time();
bench_output = run_bench(bench_inputs);
result = time() - start_time;
为了确保基准测试留在关键部分:
start_time = time();
DoNotOptimize(bench_inputs);
bench_output = run_bench(bench_inputs);
DoNotOptimise(bench_output);
result = time() - start_time;
我不明白的是,为什么这能保证(确实吗?)run_bench()
不会被移到start_time = time()
之上。
(有人在这条评论中问了完全相同的问题,但我不理解答案。)
据我所知,上面的DoNotOptimze()
做了几件事情:
- 它将
value
强制推入栈中,因为它是通过C++引用传递的。你不能有一个指向寄存器的指针,所以它必须在内存中。 - 由于
value
现在在堆栈上,随后破坏内存(如在asm约束中所做的那样)将迫使编译器假定value
被调用DoNotOptimize(value)
读取和写入。 - (我不确定
+r,m
约束是否相关。据我所知,这表示指针本身可以存储在寄存器或内存中,但指针值本身可能被读取和/或写入。)
这就是我感到困惑的地方。
如果start_time
也是堆栈分配的,则DoNotOptimize()
中的内存破坏意味着编译器必须假设DoNotOptimize()
可能会读取start_time
。因此语句的顺序只能是:
start_time = time(); // on the stack
DoNotOptimize(bench_inputs); // reads start_time, writes bench_inputs
bench_output = run_bench(bench_inputs)
但是,如果
start_time
没有存储在内存中,而是存储在寄存器中,那么破坏内存就不会破坏start_time
,对吗?在这种情况下,start_time = time()
和DoNotOptimize(bench_inputs)
的期望顺序将丢失,编译器可以自由地执行:DoNotOptimize(bench_inputs); // only writes bench_inputs
bench_output = run_bench(bench_inputs)
start_time = time(); // in a register
显然我误解了一些东西。有人可以帮忙解释一下吗?谢谢 :) 我在想这是否是因为重新排序优化发生在寄存器分配之前,因此在那时假定所有内容都是堆栈分配的。但如果是这样的话,那么
DoNotOptimize()
将是多余的,因为ClobberMemory()
就足够了。