在基准测试代码中使用volatile来阻止编译器优化

Using volatile to prevent compiler optimization in benchmarking code?

本文关键字:编译器 优化 volatile 基准测试 代码      更新时间:2023-10-16

我正在创建一个小程序来测量类型为boost::shared_ptrboost::intrusive_ptr的容器之间的性能差异。为了防止编译器优化副本,我将变量声明为volatile。循环如下:

// TestCopy measures the time required to create n copies of the given container.
// Returns time in milliseconds.
template<class Container>
time_t TestCopy(const Container & inContainer, std::size_t n) {
    Poco::Stopwatch stopwatch;
    stopwatch.start();
    for (std::size_t idx = 0; idx < n; ++idx)
    {
        volatile Container copy = inContainer; // Volatile!
    }
    // convert microseconds to milliseconds
    return static_cast<time_t>(0.5 + (double(stopwatch.elapsed()) / 1000.0));
}

其余的代码可以在这里找到:main.cpp.

  • 在这里使用volatile会阻止编译器优化副本吗
  • 是否存在可能使结果无效的陷阱

更新

回应@Neil Butterworth。即使在使用副本的时候,在我看来编译器仍然可以很容易地避免副本:

for (std::size_t idx = 0; idx < n; ++idx)
{
    // gcc won't remove this copy?
    Container copy = inContainer;
    gNumCopies += copy.size();        
}

C++03标准规定,对易失性数据的读取和写入是可观察的行为(C++2003,1.9[interro.execution]/6(。我相信这保证了对易失性数据的分配不会被优化掉。另一种可观察的行为是对I/O函数的调用。C++11标准在这方面更加明确:在1.9/8中,它明确表示

对一致性实施的最低要求是:
--对易失性对象的访问严格按照抽象机器的规则进行评估。

如果编译器能够证明代码没有产生可观察的行为,那么它就可以优化代码。在更新中(不使用volatile(,复制构造函数和其他函数调用&重载运算符可能会避免任何I/O调用和对易失性数据的访问,编译器可能会很好地理解这一点。但是,如果gNumCopies是一个全局变量,后来在具有可观察行为的表达式(例如打印(中使用,则不会删除此代码。

Volatile不太可能达到您对非POD类型的期望。我建议将容器的char *void *别名传递给另一个转换单元中的空函数。由于编译器无法分析指针的使用情况,这将充当编译器内存屏障,迫使对象至少进入处理器缓存,并阻止大多数死值消除优化。

为什么要这样做?最好的解决方案是以某种方式使用容器,比如将容器的大小添加到全局变量中。