volatile与side effect是否相关?cppreference计时代码疑问
问题1:注释的意图及与volatile sink的关系
注释make sure it's a side effect的核心目的就是强制让这行赋值操作成为编译器不能忽略的“副作用”,和volatile int sink直接绑定,本质就是为了防止编译器优化掉整个耗时操作链。
如果没有这个赋值或者sink不是volatile,编译器会发现:我们创建了vector、调用了accumulate,但最终计算出来的结果既没打印也没被后续代码使用——这种“无意义”的操作会被编译器直接优化掉,导致计时结果完全失真(可能显示耗时为0)。加上这行赋值并配合volatile,就是告诉编译器:这个计算结果必须被写到sink里,这个操作是有实际意义的,不能省略。
问题2:为什么要给sink加volatile修饰符
你可能觉得编译器能识别accumulate修改了sink,但编译器的优化逻辑比你想的更激进:如果sink是普通int,编译器会发现后续代码根本没用到sink的值,那std::accumulate的计算结果以及vector的创建都是“无用功”,会被直接优化删除。
而volatile的作用就是告诉编译器,对这个变量的读写操作是“不可优化”的——无论后续有没有用到它的值,对sink的赋值都必须执行。这样一来,为了完成这个赋值,编译器就必须保留前面的vector初始化和accumulate计算,计时才能准确反映真实的操作耗时。
volatile与side effect的关联
先明确两个核心概念:
- side effect(副作用):指程序对外部状态的修改,比如修改全局变量、执行IO操作、调用有副作用的函数等。编译器会保证有副作用的操作不会被随意优化。
- volatile:修饰的变量的读写本身就被视为一种side effect。编译器必须严格按照代码顺序执行对volatile变量的读写,不能省略、重排或合并这些操作。
回到这段代码:把sink设为volatile,就是让sink = std::accumulate(...)这行成为一个明确的side effect。因为要完成这个副作用,前面的vector创建和accumulate计算都是必要步骤,编译器就不会把它们优化掉,这样计时才能得到真实的操作时间。
附上相关代码
#include <iostream> #include <iomanip> #include <vector> #include <numeric> #include <chrono> volatile int sink; int main() { std::cout << std::fixed << std::setprecision(9) << std::left; for (auto size = 1ull; size < 1000'000'000ull; size *= 100) { // record start time auto start = std::chrono::system_clock::now(); // do some work std::vector<int> v(size, 42); // This is the line I mentioned. sink = std::accumulate(v.begin(), v.end(), 0u); // make sure it's a side effect // record end time auto end = std::chrono::system_clock::now(); std::chrono::duration<double> diff = end - start; std::cout << "Time to fill and iterate a vector of " << std::setw(9) << size << " ints : " << diff.count() << " s\n"; } }
内容的提问来源于stack exchange,提问作者starriet 차주녕

