【问题标题】:for loop bounds recalculated each iteration?for循环边界重新计算每次迭代?
【发布时间】:2015-05-15 04:40:12
【问题描述】:

假设你有一个这样的类:

class A {
public:
    A(const size_t m, const size_t n) :
        m_(m), n_(n), data_(m*n ? new double[m*n] : nullptr) {}
    ~A() { delete[] data_; }

    void foo() const {
        // m_*n_ evaluated each iteration?
        for (size_t i = 0; i<m_*n_; ++i)
            data_[i] = 0.0;

        // this probably wont but what about the above?
        for (auto& i : *this)
            i = 0.0;
    }

    double* begin() const { return data_; }
    double* end() const { return data_ + m_*n_; }

private:
    size_t m_, n_;
    double* data_;
};

由于 foo 是 const 而 m_,n_ 是成员变量,编译器应该知道这些不能改变。是每次都对产品进行评估,还是编译器会对其进行优化?

【问题讨论】:

  • 这取决于编译器、它的具体优化设置、火星和月球的相对位置等。但这真的很重要吗?你有没有通过基准测试发现这是您代码中的一个重要瓶颈吗?如果没有,请不要打扰。
  • 您可以随时查看程序集,看看它在做什么。
  • 实际上,编译器应该看到m_n_ 在循环期间不会发生变化,并将计算提升到循环之外。
  • 为什么不在循环之前进行计算并将结果发送到另一个变量,然后用于循环绑定?这样您就可以 100% 确定您的编译器不会在每次迭代时重新计算乘积。我不知道编译器是否重新计算,但我知道如果您使用字符串长度函数来确定长度并且这在循环范围内,例如for(int i=0; i&lt;strlen(mystring); i++),编译器将重新计算字符串长度每次。
  • loop condition evaluation的可能重复

标签: c++ c++11 for-loop optimization


【解决方案1】:

问题一:

   // m_*n_ evaluated each iteration?
    for (size_t i = 0; i<m_*n_; ++i)
        data_[i] = 0.0;

回答:可能,但你永远不知道。编译器变得非常聪明。

您可以通过将m_n_ 声明为const 成员来大大提高几率。无论如何,您应该这样做,恕我直言,因为该类的逻辑需要它;如果m_n_ 发生变化,则需要重新分配data_。将变量声明为const 告诉编译器即使它看不到类的所有成员函数的所有定义,也允许假设m_n_ 不变。

问题 2:

    for (auto& i : *this)
        i = 0.0;

在这种情况下,根据定义,将不会重新计算最终值。这就是定义语句范围的方式。

一般来说,两者之间有相当大的区别:

 for (auto p = container.begin(), lim = container.end();
      p != lim;
      ++p) { ... }

 for (auto p = container.begin();
      p != container.end();
      ++p) { ... }

在第一种情况下,假设容器在循环期间没有被修改,因为修改会使迭代器无效(例如lim 迭代器)。 (有些容器类型不是这样。但对于大多数容器类型,应避免在循环期间进行修改。)

在第二种情况下,每次都会计算限制值,因此如果容器被修改,则没有问题。至少,最终测试没有问题。您仍然需要确保p 不会失效,并且对于某些容器类型——例如无序映射和集合——元素的顺序可以通过修改来改变,因此添加新元素可能会导致某些元素从迭代中丢失。

语法for ( variable : expression) 的范围被明确定义为转换为上述迭代样式中的第一个,其中限制在迭代开始时计算一次,因此可能使限制迭代器无效的突变导致未定义行为。

在大多数情况下,您应该避免此类突变,并且应该使用范围语法或等效样式。但是在某些应用中,每次都重新计算限制是合适的。这种用例的最佳示例可能是工作列表,可以使用std::deque 建模:

for (auto work = workqueue.begin();
     work != workqueue.end();
     ++work) {
  /* ... */
  if (some_condition) {
    work_queue.emplace_back(work_item);
  }
  /* ... */
}

例如,这是图算法中的一种常见风格。

【讨论】:

    猜你喜欢
    • 2012-10-27
    • 1970-01-01
    • 1970-01-01
    • 2016-05-20
    • 2018-02-13
    • 1970-01-01
    • 1970-01-01
    • 2013-10-11
    • 1970-01-01
    相关资源
    最近更新 更多