超速矢量push_back

时间:2017-06-02 10:29:13

标签: c++ stdvector push-back

我正在尝试在无法预测容量时加速vector :: push_back

当保留可用时,矢量push_back将新元素写入容器的末尾,然后移动结束标记。使用所有保留后,push_back可能会触发重新分配,这是一个缓慢的过程   为了加快速度,可以为几个即将到来的push_back重新生成保留,而不会在空时重新分配。您如何看待此代码有助于实现该目标?

#ifndef __VECTOR_HPP
#define __VECTOR_HPP
#include <exception>
#include "Concept.hpp" //Concept::RESA constant
#include <vector>
template <typename T>
class Vector : public std::vector<T> {
public :
  void push_back (T t) {
    if (std::vector<T>::size () == std::vector<T>::capacity ()) {
      std::vector<T>::reserve ((size_t) Concept::RESA);
    }
    std::vector<T>::push_back (t);
  }
};
#endif

测试程序:

#include "Vector.hpp"

int main (int argc, char* argv []) {
  {
    std::vector<size_t> v0;
    clock_t t (clock ());
    size_t duration (0);
    for (size_t i (0); i != 10000000; i++) {
      v0.push_back (i);
    }
    duration = (size_t) (clock () -t);
    std::cout << "duration old push_back == " << duration << " ticks" << std::endl;
  }
  {
    size_t duration (0);
    Vector<size_t> v1;
    clock_t t (clock ());
    for (size_t i (0); i != 10000000; i++) {
      v1.push_back (i);
    }
    duration = (size_t) (clock () -t    );
    std::cout << "duration new push_back == " << duration << " ticks" << std::endl;
  }
}

结果:

使用Concept :: RESA == 8192,并应用建议,以下是Lenovo ThinkCentre icore5(Linux Debian,g ++)上的结果:

持续时间旧push_back == 105317滴答

持续时间new push_back == 87156 ticks

2 个答案:

答案 0 :(得分:3)

确实push_back 可能会触发重新分配,这是一个缓慢的过程 它不会在每个push_back上执行此操作,而是每次都会保留指数级更多的内存,所以如果事先对结果矢量大小有一个很好的近似,那么显式reserve才有意义。

换句话说,std::vector已经在您的代码中处理了您的建议。

另一点:there is a reserve method比插入和擦除元素更有效,最明显的是它不会创建和销毁实际对象。

具有讽刺意味的是,@Sopel提到用类中的reserve替换插入/擦除将禁用向量的增长摊销,使您的代码成为几个错误(有些)相互抵消的好例子。

答案 1 :(得分:2)

您的代码存在一些问题。

  • 您实际上定义的类型与std::vector非常相似,std::vector是其唯一成员。为什么不首先使用std::vector
  • 您的push_back()功能非常糟糕。我先解释std::vector<>::push_back()实际上做了什么。

    1. if size()<capacity():它只是复制块末尾的新元素并递增end标记。 这是最常见的情况
    2. 如果size()==capacity(),则需要重新分配

      1. 它分配一个新的内存块,通常是当前容量的两倍
      2. 它将所有数据移动到新块的开头
      3. 取消分配旧的内存块
      4. 它最终在数据末尾构建一个新元素
    3. 现在让我们看看你的

      void push_back (const T& t) {
        if (val_.size () == val_.capacity ()) {
          val_.insert (val_.end (), resa_.begin (), resa_.end ());
          auto i = val_.end();
          i -= (size_t) Concept::RESA;
          val_.erase (i, val_.end ());
        }
        val_.push_back (t);
      }
      

      如果val_.size()==val_.capacity()

      1. insert()的{​​{1}}默认构造元素。为此,val_.end()执行以下操作:
        1. 它分配了一个新的内存块,足够大以容纳旧数据和要插入的数据,但可能更大。
        2. 它将所有数据移动到新块的开头
        3. 取消分配旧的内存块
        4. 它复制要插入旧数据末尾的元素
      2. 它会破坏所有新插入的元素。
      3. 如果最终在数据末尾构造一个新元素(不需要重新分配)。
      4. 因此,您的函数也需要像普通std::vector::insert() 那样频繁地重新分配,并完全不必要的复制构造,然后销毁一大块元素。当你想要连续的内存布局时,无法避免重新分配(如std::push_back()所承诺的那样)不知道最终的大小预先。如果可以删除这些要求中的任何一个,则可以避免重新分配:std::vector或使用具有非连续内存的容器,例如std::deque