MSVC中如何防止函数重排序以准确测量函数执行时间?
问题解答
1. 这种重排序是否符合C++标准?
是的,该优化完全符合C标准。C的as-if规则允许编译器在不改变程序可观察行为的前提下,任意调整指令执行顺序。你的代码中,test函数的执行结果仅影响后续的std::cout << pi,而steady_clock::now()的调用本身无副作用(标准规定时钟函数调用不引入数据依赖或可见副作用)。因此编译器判定,将t0、t1的调用移到test前后不会改变程序输出结果(只要最终pi值正确输出即可),于是执行了重排优化。
2. 如何在开启其他优化的前提下禁用这种重排序?
需要在时间测量点与test函数调用之间建立编译器无法忽略的依赖关系,强制保证执行顺序。以下是几种可靠方案:
方法一:使用std::atomic强制内存可见性
将pi声明为std::atomic<double>,test结果写入pi时会产生内存屏障,阻止编译器重排:
#include <iostream> #include <chrono> #include <atomic> __declspec(noinline) double test(int num_intervals) { double dx = 1.0 / num_intervals; double sum = 0.0; for (int i = 0; i < num_intervals; ++i) { double x = (i + 0.5) * dx; double h = std::sqrt(1 - x * x); sum += h * dx; } double pi = 4 * sum; return pi; } int main() { int num_intervals = 1000000000; double serial_time = 0.0; std::atomic<double> pi; auto t0 = std::chrono::steady_clock::now(); pi.store(test(num_intervals), std::memory_order_seq_cst); auto t1 = std::chrono::steady_clock::now(); serial_time = std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count(); std::cout << "serial pi == " << pi.load(std::memory_order_seq_cst) << std::endl; std::cout << "serial_time == " << serial_time << " ms" << std::endl; return 0; }
std::memory_order_seq_cst会强制编译器和CPU不进行跨屏障的指令重排,确保test的执行严格在t0和t1之间。
方法二:MSVC专用:利用volatile阻止重排
通过读取volatile变量引入编译器无法优化的“副作用”,强制停顿重排:
int main() { int num_intervals = 1000000000; double serial_time = 0.0; volatile int dummy = 0; auto t0 = std::chrono::steady_clock::now(); dummy; // 阻止时间点重排到test之前 double pi = test(num_intervals); dummy; // 阻止test重排到t1之后 auto t1 = std::chrono::steady_clock::now(); serial_time = std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count(); std::cout << "serial pi == " << pi << std::endl; std::cout << "serial_time == " << serial_time << " ms" << std::endl; return 0; }
注意:volatile仅保证变量读写不被优化,不保证跨平台内存屏障效果,但在MSVC环境下可有效阻止编译器重排。
方法三:引入不可优化的IO副作用
在时间测量点之间写入一个流,引入编译器无法消除的副作用:
int main() { int num_intervals = 1000000000; double serial_time = 0.0; auto t0 = std::chrono::steady_clock::now(); double pi = test(num_intervals); std::clog << std::noboolalpha << pi; // 引入副作用,阻止重排 auto t1 = std::chrono::steady_clock::now(); serial_time = std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count(); std::cout << "serial pi == " << pi << std::endl; std::cout << "serial_time == " << serial_time << " ms" << std::endl; return 0; }
此方法会带来额外IO开销,可能影响时间测量准确性,仅适合快速验证。
为什么_ReadWriteBarrier和std::atomic_thread_fence没生效?
_ReadWriteBarrier是MSVC旧版编译器屏障,在高优化等级(如/O2)下可能被忽略——它不涉及实际内存访问,编译器可判定无实际副作用。std::atomic_thread_fence需配合实际原子操作才能生效,单独调用时编译器会认为无实际意义,从而忽略;必须在屏障前后有原子变量的读写操作,才能触发内存屏障效果。
内容的提问来源于stack exchange,提问作者AdamF
相关产品推荐
相关产品推荐

