【问题标题】:Using volatile to prevent compiler optimization in benchmarking code?在基准测试代码中使用 volatile 防止编译器优化?
【发布时间】:2011-09-02 01:45:18
【问题描述】:

我正在创建一个小程序来测量 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();        
}

【问题讨论】:

  • 在编译器中使用 -O0 和 -g 标志不起作用吗?我不认为在这里使用 volatile 是正确的方法。
  • @RC 而不是使用-O0 进行分析,您可以只猜测性能影响。结果同样与现实场景无关。
  • @RC 我不确定。我知道,即使使用 -O0,像 RVO 这样的优化也会发挥作用(参见stackoverflow.com/questions/4767620/…)。
  • 如果拷贝构造函数有副作用,编译器是否允许优化掉拷贝?
  • @Neil,你能详细说明一下吗?具体来说,我认为 12.8/15 中列出的任何一个条件都不适用于这种情况。没有返回语句,也没有临时对象。此外,请考虑 ideone.com/aJBr1 。即使使用g++ -O4,复制构造函数也会在每个循环中调用一次。 (旁白:是的,如果有问题的 Container 有一个没有副作用的复制构造函数,则可以按照 1.9/1 和 1.9/6 对其进行优化。但是,我问的是有副作用的复制构造函数.)

标签: c++


【解决方案1】:

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

【讨论】:

  • 编译器似乎仍然可以优化此处的副本。请参阅我的编辑以获取代码示例。
  • 哦,好的 - 然后在副本和原件上调用一个函数。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    对一致性实现的最低要求是:
    — 对 volatile 对象的访问严格按照抽象机的规则进行评估。

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

    【讨论】:

      猜你喜欢
      • 2017-02-28
      • 2011-09-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多