Google的DoNotOptimize()函数是如何强制保证语句执行顺序的?
DoNotOptimize 工作原理解答
核心问题答复
你描述的写法完全可以保证run_bench()不会被重排到start_time = time()之前,核心逻辑围绕GCC/Clang内联汇编的规则展开,我们逐段拆解实现和约束:
实现细节拆解
你贴出的DoNotOptimize定义每部分作用如下:
template <class Tp> inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp& value) { asm volatile("" : "+r,m"(value) : : "memory"); }
asm volatile:volatile修饰的内联汇编会被编译器认定为存在未知副作用,既不能被优化删除,也不能和其他有副作用的操作重排执行顺序。"+r,m"(value):输出约束,+表示该汇编块对value是读写操作,r,m表示value可以存放在寄存器或者内存中,不需要强制落栈。这里的约束直接告诉编译器:该汇编块会直接操作value的值,不管它存在寄存器还是内存里。"memory"clobber:告诉编译器该汇编块会读写任意未知地址的内存,因此执行该汇编块前,所有缓存到寄存器的内存修改必须写回内存;执行后,所有需要读取的内存值必须重新从内存加载,不能用寄存器缓存的旧值。
常见误解纠正
DoNotOptimize不会强制变量落栈
引用传递不需要变量一定存在内存中,编译器完全可以将引用优化为寄存器别名,+r,m的设计就是允许变量保留在寄存器中,避免额外的内存访问开销,不影响基准测试的准确性。- 寄存器存储的
start_time不会导致重排
重排规则不仅和内存依赖有关,更核心的约束是:编译器不能交换两个有可见副作用的操作的顺序,除非能证明交换后不影响执行结果。time()是系统调用,属于有全局副作用的外部函数,asm volatile也属于有未知副作用的操作,二者的执行顺序永远不会被编译器交换,和start_time存在寄存器还是内存无关。 - 为什么不能只用
ClobberMemory()ClobberMemory()的实现只有空的asm volatile加memoryclobber,只能约束内存操作的重排,无法感知存在寄存器中的变量。DoNotOptimize通过+r,m的输出约束,将目标变量(无论存储位置)纳入汇编块的依赖链,哪怕变量完全在寄存器中,编译器也知道该汇编块会操作这个变量,因此不会打乱变量相关操作的顺序,这是ClobberMemory做不到的。
执行顺序保证的逻辑
你给出的基准测试代码顺序不会被打乱,依赖三层约束:
start_time = time()是有全局副作用的系统调用,不能和后续的asm volatile块重排。DoNotOptimize(bench_inputs)声明会读写bench_inputs,依赖bench_inputs的run_bench()只能在该调用之后执行。DoNotOptimize(bench_output)声明会读写bench_output,作为run_bench()返回值的消费者,run_bench()只能在该调用之前执行。
三层约束共同作用下,run_bench()被严格限制在两次计时调用之间执行,不会出现重排问题。
另外注意你示例中存在拼写错误:DoNotOptimise应为DoNotOptimize。
内容的提问来源于stack exchange,提问作者Edd Barrett
相关产品推荐
相关产品推荐

