使用volatile关键字在基准测试代码中防止编译器优化?

10

我正在创建一个小程序,用于测量类型为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();        
}

1
在编译器中使用 -O0 和 -g 标志无效吗?我认为在这里使用 volatile 不是正确的方法。 - RC.
6
与其使用“-O0”进行性能分析,不如猜测性能影响。结果同样与实际情境无关。 - Konrad Rudolph
1
@RC 我不确定。我知道像 RVO 这样的优化即使在使用 -O0 时也会启动(参见 https://dev59.com/rG445IYBdhLWcg3wnrvM)。 - StackedCrooked
如果复制构造函数具有副作用,编译器是否允许优化掉复制? - Robᵩ
@Rob,编译器可以优化任何它能够证明对程序结果没有影响的内容。因此,如果你创建并立即销毁一个对象,则从原则上讲,只要编译器能够证明没有持久性副作用,就可以完全优化掉整个过程。 - bdonlan
显示剩余4条评论
3个回答

10
C++03标准规定,对易失性数据的读写是可观察行为(C++ 2003, 1.9 [intro.execution] / 6)。我相信这保证了易失性数据的赋值不会被优化掉。另一种可观察行为是调用输入/输出函数。 在这方面,C++11标准甚至更加明确:在1.9/8中,它明确表示
“符合实现的最小要求是:
— 对易失对象的访问严格按照抽象机器的规则进行评估。”
如果编译器能够证明代码不会产生可观察行为,则可以将代码优化掉。在您的更新中(未使用易失性数据),复制构造函数和其他函数调用及重载运算符可能避免任何I/O调用和易失性数据的访问,编译器可能会理解它。但是,如果gNumCopies是一个全局变量,稍后在具有可观察行为的表达式(例如打印)中使用,则此代码将不会被删除。

4

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


仍然有可能编译器会优化掉这里的复制。请参见我的编辑以获取代码示例。 - StackedCrooked
哦,好的 - 然后在副本和原始数据上调用一个函数。 - user2100815

4

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接