【问题标题】:speeding vector push_back [closed]超速矢量push_back [关闭]
【发布时间】:2017-11-03 17:37:19
【问题描述】:

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

当保留可用时,向量 push_back 将新元素写入容器的末尾,然后移动结束标记。使用完所有预留后,push_back 可能会触发重新分配,这是一个缓慢的过程。
为了加快速度,reserve 会为几个即将到来的 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++)上的结果:

duration old push_back == 105317 tick

新 push_back 持续时间 == 87156 滴答声

【问题讨论】:

  • 你到底想用这段代码实现什么?
  • 问题陈述是什么?
  • @WhozCraig 直接以恒定增量调用 reserve 将使插入 O(n^2)。通过插入,他仍然获得指数增长,因为它是由 std::vector 处理的。但这并没有改变这样一个事实,即这段代码不仅毫无意义,而且做了一些他认为不应该做的事情。
  • 我试图解决这个问题,但当我意识到糟糕的措辞与对std::vector 的糟糕理解相匹配时,我就回滚了。解决问题使其毫无意义。特别是,虽然第二句谈到“reserve is available”,但这确实not 表示vector::reserve。这可能意味着vector::capacity() greater than vector::size()
  • @Ap31:我想我明白你的意思了。我想 OP 的结果评论是向量的结果重新分配不会是 resa_.begin()resa_.end() 之间的距离,但无论 vector 的重新分配策略是什么(可能是基础缓冲区的简单加倍)

标签: c++ stdvector push-back


【解决方案1】:

确实push_back可能触发重新分配,这是一个缓慢的过程。
但是,它不会在每个push_back 上都这样做,而是每次都会以指数方式保留更多的内存,因此明确的reserve 只有在您事先对结果向量大小有一个很好的近似值时才有意义。

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

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

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

【讨论】:

  • 如果向量已经“照顾到我的代码建议的内容”,你如何解释速度提高了 30%?
  • @Saint-Martin 可能是由于缺乏适当的测量 - 你没有提到你的操作系统、编译器、编译器标志,不清楚你的 clock() 做了什么,没有预热,测量是连续的=> 不对称等。
  • 我的测量值(相同来源,MSVC14 -O2)也显示没有差异
【解决方案2】:

您的代码有几个问题。

  • 您实际上是在定义一个与std::vector 非常相似的类型,而std::vector 作为其唯一成员。为什么不首先使用std::vector
  • 你的push_back() 功能太糟糕了。让我先解释一下std::vector&lt;&gt;::push_back() 的实际作用。

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

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

    现在让我们看看你的

    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()s 默认构造的元素位于val_.end()。为此,std::vector::insert() 执行以下操作:
      1. 它分配一个新的内存块,足够大以容纳旧数据和要插入的数据,但可能更大。
      2. 它将所有数据移动到新块的开头
      3. 它取消分配旧的内存块
      4. 它复制要插入到旧数据末尾的元素
    2. 它会破坏所有新插入的元素。
    3. if finally 在数据末尾构造一个新元素(无需重新分配)。

    因此,您的函数也需要重新分配与普通 std::push_back() 一样频繁,并且完全不必要地复制构造然后销毁整个元素块。当您想要连续的内存布局(如std::vector 所承诺的那样)并且事先不知道最终大小时,无法避免重新分配。如果可以放弃这些要求中的任何一个,则可以避免重新分配:通过std::vector&lt;&gt;::reserve() 或使用具有非连续内存的容器,例如std::deque

【讨论】:

  • 我的向量不会避免重新分配:它只是对它们进行分组。向量比双端队列快。
  • @Saint-Martin:您能否详细说明一下您的向量如何对其重新分配进行分组?
  • @Saint-Martin 向量比双端队列快取决于什么!如果你有很多push_back()insert(),甚至push_front()std::deque 会更快...
  • @Saint-Martin 您的代码重新分配的频率与普通的std::vector&lt;&gt;::push_back() 一样频繁。特别是,没有分组,也没有任何好处。
  • 检查测试结果。不太确定 deque 对于 push_back 是最快的(具有保留的向量应该更快)。是否需要重做测试并将 Vector 与 deque 进行比较?
猜你喜欢
  • 1970-01-01
  • 2022-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多