【问题标题】:Faster range for loop (C++11)更快的循环范围 (C++11)
【发布时间】:2012-06-13 05:13:48
【问题描述】:

我有一些代码可以遍历(多变量)数值范围:

#include <array>
#include <limits>
#include <iostream>
#include <iterator>

template <int N>
class NumericRange : public std::iterator<double, std::input_iterator_tag>
{
public:
  NumericRange() {
    _lower.fill(std::numeric_limits<double>::quiet_NaN());
    _upper.fill(std::numeric_limits<double>::quiet_NaN());
    _delta.fill(std::numeric_limits<double>::quiet_NaN());
  }
  NumericRange(const std::array<double, N> & lower, const std::array<double, N> & upper, const std::array<double, N> & delta):
    _lower(lower), _upper(upper), _delta(delta) {
    _state.fill(std::numeric_limits<double>::quiet_NaN());
  }

  const std::array<double, N> & get_state() const {
    return _state;
  }

  NumericRange<N> begin() const {
    NumericRange<N> result = *this;
    result.start();
    return result;
  }

  NumericRange<N> end() const {
    NumericRange<N> result = *this;
    result._state = _upper;
    return result;
  }

  bool operator !=(const NumericRange<N> & rhs) const {
    return in_range();
    //    return ! (*this == rhs);
  }

  bool operator ==(const NumericRange<N> & rhs) const {
    return _state == rhs._state && _lower == rhs._lower && _upper == rhs._upper && _delta == rhs._delta;
  }

  const NumericRange<N> & operator ++() {
    advance();
    if ( ! in_range() )
      _state = _upper;
    return *this;
  }

  const std::array<double, N> & operator *() const {
    return _state;
  }

  void start() {
    _state = _lower;
  }

  bool in_range(int index_to_advance = N-1) const {
    return ( _state[ index_to_advance ] - _upper[ index_to_advance ] ) < _delta[ index_to_advance ];
  }

  void advance(int index_to_advance = 0) {
    _state[ index_to_advance ] += _delta[ index_to_advance ];
    if ( ! in_range(index_to_advance) ) {
      if (index_to_advance < N-1) {
    // restart index_to_advance
    _state[index_to_advance] = _lower[index_to_advance];

    // carry
    ++index_to_advance;
    advance(index_to_advance);
      }
    }
  }
private:
  std::array<double, N> _lower, _upper, _delta, _state;
};

int main() {
   std::array<double, 7> lower{{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}};
   std::array<double, 7> upper{{1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}};
   std::array<double, 7> delta{{0.03, 0.06, 0.03, 0.06, 0.03, 0.06, 0.03}};

  NumericRange<7> nr(lower, upper, delta);
  int c = 0;    
  for (nr.start(); nr.in_range(); nr.advance()) {
    ++c;
  }
  std::cout << "took " << c << " steps" << std::endl;    
  return 0;
}

使用g++ -std=c++11 -O3(或使用 gcc -std=c++0x)在我的计算机上运行大约 13.8 秒。

如果我将 main 函数更改为使用基于范围的 for 循环:

  for (const std::array<double, 7> & arr : nr) {
    ++c;
  }

运行时间增加到 29.8 秒。巧合的是,这约 30 秒的运行时间与使用 std::vector&lt;double&gt; 而不是 std::array&lt;double, N&gt; 时的原始运行时间几乎相同,这让我相信编译器无法展开范围生成的代码-基于for循环。

有没有办法在保持原始速度的同时仍然使用基于范围的 for 循环?


我的尝试:

通过更改NumericRange 中的两个成员函数,我可以使用基于范围的for 循环获得所需的速度:

bool operator !=(const NumericRange<N> & rhs) const {
  return in_range();
  //    return ! (*this == rhs);
}

const NumericRange<N> & operator ++() {
  advance();
  //    if ( ! in_range() )
  //      _state = _upper;
  return *this;
}

但是,这段代码感觉设计得很糟糕,因为!= operator 不能按预期工作 通常对于数字运算,我使用&lt; 来终止循环而不是==。我想找到第一个超出范围的值,但由于数值错误,分析上这样做可能无法给出准确的答案。

如何强制 != operator 表现得与 &lt; 相似而不误导其他会看到我的代码的人?我只需将 begin()end() 函数设为私有,但对于基于范围的 for 循环,它们需要公开。

非常感谢您的帮助。

【问题讨论】:

  • 您的beginend 实现非常昂贵。
  • @ildjarn: 这应该没问题,因为它们只被调用一次。
  • @KarolyHorvath 肯定是这样。我真的很想成为一个更好的程序员,并且对像你这样批评但不提供任何帮助的 cmets 感到灰心。您认为您可以通过解释改进我的代码和设计的方法来提供帮助吗?
  • 可能是一个愚蠢的建议..但是您的end 不会为循环的每次迭代调用吗?这会阻碍性能。
  • @Asha:不,range-for 语法保证它只会被调用一次。只是operator++operator==太贵了。

标签: c++ optimization iterator c++11


【解决方案1】:

就我而言,问题在于您没有正确使用 range-for 构造。


让我们退后一步:

void foo(std::vector<int> const& v) {
    for (int i: v) {
    }
}

注意 range-for 如何遍历向量以提取 整数


由于某些原因,您选择不实现从 beginend 的迭代器,而是重新使用您正在迭代的内容的副本,即使它变化很小,而且您正在做 的额外工作(在副本和支票中)...

注意:std::iterator&lt;double, ...&gt; 表示operator* 应该返回一个double&amp;

如果你想使用新的成语,你必须符合它的期望。

期望您使用 迭代器 进行迭代,而不是一遍又一遍地复制原始对象(稍作修改)。这是 C++ 习语。

这意味着您需要将对象切成两半:在要迭代的对象中删除在迭代期间不可变的所有内容以及在迭代器中修改的内容。

据我所知:

  • _lower_upper_delta 已修复
  • _state 是迭代变量

因此,你会:

template <typename> class NumericRangeIterator

template <unsigned N> // makes no sense having a negative here
class NumericRange {
public:
    template <typename> friend class NumericRangeIterator;

    typedef NumericRangeIterator<NumericRange> iterator;
    typedef NumericRangeIterator<NumericRange const> const_iterator;

    static unsigned const Size = N;

    // ... constructors

    iterator begin(); // to be defined after NumericRangeIterator
    iterator end();

    const_iterator begin() const;
    const_iterator end() const;

private:
    std::array<double, N> _lower, _upper, _delta;
}; // class NumericRange

template <typename T>
class NumericRangeIterator: public
    std::iterator< std::array<double, T::Size>,
                   std::forward_iterator_tag >
{
public:
    template <unsigned> friend class NumericRange;

    NumericRangeIterator(): _range(0), _state() {}

    NumericRangeIterator& operator++() {
        this->advance();
        return *this;
    }

    NumericRangeIterator operator++(int) {
        NumericRangeIterator tmp(*this);
        ++*this;
        return tmp;
    }

    std::array<double, T::Size> const& operator*() const {
        return _state;
    }

    std::array<double, T::Size> const* operator->() const {
        return _state;
    }

    bool operator==(NumericRangeIterator const& other) const {
        return _state != other._state;
    }

    bool operator!=(NumericRangeIterator const& other) const {
        return !(*this == other);
    }


private:
    NumericRangeIterator(T& t, std::array<double, T::Size> s):
        _range(&t), _state(s) {}

    void advance(unsigned index = T::Size - 1);  // as you did
    void in_range(unsigned index = T::Size - 1); // as you did

    T* _range;
    std::array<double, T::Size> _state;
}; // class NumericRangeIterator


template <unsigned N>
auto NumericRange<N>::begin() -> typename NumericRange<N>::iterator {
    return iterator(*this, _lower);
}

template <unsigned N>
auto NumericRange<N>::end() -> typename NumericRange<N>::iterator {
    return iterator(*this, _upper);
}

通过所有这些设置,您可以编写:

for (auto const& state: nr) {
}

其中auto 将被推导出为std::array&lt;double, nr::Size&gt;

注意:不确定iterator 是否有用,可能只有const_iterator,因为它在某种程度上是错误的迭代;您无法通过迭代器访问范围对象来修改它。

编辑: operator== 太慢了,如何让它变得更好?

我建议作弊。

1/修改迭代器的构造函数

NumericRangeIterator(): _range(0), _state() {}               // sentinel value
NumericRangeIterator(T& t): _range(&t), _state(t._lower) {}

2/ 调整迭代以在最后创建新的“哨兵”值

void advance() {
    // ...

    if (not this->in_range()) {        // getting out of the iteration ?
       *this = NumericRangeIterator(); // then use the sentinel value
    }
}

3/ 相应地更改 beginend 定义

template <unsigned N>
auto NumericRange<N>::begin() -> typename NumericRange<N>::iterator {
    return iterator(*this);
}

template <unsigned N>
auto NumericRange<N>::end() -> typename NumericRange<N>::iterator {
    return iterator();
}

4/ 使用哨兵使== 更加平等

bool operator==(NumericRangeIterator const& other) const {
    return _range == other._range and _state == other._state;
}

现在,在整个迭代过程中,== 都是短路的,因为 _range 之一为空而另一个不是。只有在最后一次调用时,两个_state 属性的比较才会真正发生。

【讨论】:

  • NumericRange 如何访问NumericRangeIterator 的私有构造函数NumericRangeIterator(T&amp; t, std::array&lt;double, T::Size&gt; s)?友谊应该是互惠的吗?
  • @ildjarn:哎呀,确实如此。我辩论了一会儿,让构造函数公开,我担心我迷路了,谢谢你抓住这个。
  • @MatthieuM。不错的组织。然而问题是一样的:用!= 测试迭代器并不能像编码的那样工作,因为它需要它们在结尾处完全相同。将!= 运算符更改为返回in_range() 运行时间为19 秒,但会误导其他查看代码的人。在 ++(前缀)运算符 if ( ! in_range() ) _state = _range-&gt;_upper; 中添加一行可与编码的 != 运算符一起使用,但需要 33 秒才能运行。太糟糕了,没有办法强制循环的范围使用start(); in_range(); advance();而不是迭代器......
  • @Oliver:不幸的是,这违背了 C++ 中迭代的所有用途(它本身源于使用指针来分隔范围)。另一方面,我认为你可以通过引入哨兵来作弊。我发现in_range 的实现比较奇怪,因为它只测试数组的一个值:也许你可以在到达末尾时“销毁”迭代器?
  • @MatthieuM。优雅的;但是,它仍然需要大约 28 秒才能运行;我认为问题不在于比较,而是编译器无法在结果代码中展开循环。性能几乎与使用 vector 而不是 array 始终相同(没有边界检查);数组版本的主要区别在于循环的大小在编译时是已知的。所以现在,看起来我可以有迭代器(和基于范围的循环),但速度要慢 2 倍...... X(
猜你喜欢
  • 2021-04-08
  • 1970-01-01
  • 2014-02-10
  • 1970-01-01
  • 1970-01-01
  • 2012-09-11
相关资源
最近更新 更多