预分配和矢量

时间:2010-12-30 08:00:04

标签: c++ vector

在c ++中,对于vector,即使向量动态分配空间,预分配也是如此重要

5 个答案:

答案 0 :(得分:9)

首先,99%的案例不太可能“如此重要”。

这基本上是一种优化。由于vector不知道你要添加多少个元素,它假设一个小的默认值,如果你试图添加一个新元素并且没有足够的空间来容纳新元素,它将不得不增长添加。 增长操作可能很昂贵,因为它可能需要分配一个全新的缓冲区,将向量的当前内容复制到新缓冲区,并释放旧缓冲区。通过预先分配足够的空间,如果您知道要添加多少元素,则可以避免不必要的增长。

与任何性能优化一样,除非是瓶颈,否则不要担心它。此外,如果您不知道要添加多少元素,请让矢量决定。不要假设随机选择的数字比实现默认值更好。

答案 1 :(得分:4)

其他答案已经涵盖了这个理论,但我还想提供一些数据来了解正在发生的事情。

我写了一个小测试套件来比较std::vector push_backreserve + push_back,在构造函数中调整大小(应该与resize相同)+ operator[]和原始动态数组;以下是10000000 int s的结果:

matteo@teoubuntu:~/cpp/vectorbenchmark2$ ./vectorbenchmark2 
Insert the number of elements: 10000000
Insert the number of iterations: 100
Minimum allocation for each benchmark 40000000 bytes.
Starting benchmark std::vector<int> (push_back/iterator, without reserve)... benchmark completed.
Results: 173.1 +/- 2.4 ms
Starting benchmark std::vector<int> (push_back/iterator, with reserve)... benchmark completed.
Results: 122.17 +/- 0.57 ms
Starting benchmark std::vector<int> (sized with constructor/operator[])... benchmark completed.
Results: 115.95 +/- 0.66 ms
Starting benchmark new int[]... benchmark completed.
Results: 121.33 +/- 0.84 ms
Starting benchmark malloc+memset... benchmark completed.
Results: 123.9 +/- 4.3 ms
Starting benchmark malloc... benchmark completed.
Results: 117.7 +/- 2.3 ms
Starting benchmark std::list<int>... benchmark completed.
Results: 552 +/- 35 ms

每个基准都包括数据结构的分配,用随机数填充并重新读取volatile变量中的所有内容。测试执行的次数与第二个参数中指定的次数相同(在此次运行中我输入100);时间用gettimeofday来衡量。显示的结果是在每个类别的所有时间计算的平均值和标准偏差。整个事情的代码可用here

我们可以从这些数据中得出什么结果?

  • 第一个明显的结果是没有预分配的push_back是最慢的方法:在这些条件下,它比使用预分配的push_back 慢约41%。因此,如果时间是一个关键因素,并且或多或少地计算vector所需要的大小并不困难,reserve可能非常方便。显然,如果你的分配+填充一般需要大约10毫秒,即使削减40%也不会真正注意到;像往常一样,优化合理的地方(即瓶颈)。
  • 另一个有趣的结果是初始化数组的大小(而不是使用reserve进行“透明”预分配)更快;我认为这是因为我们没有在紧密循环中调用push_back(必须更新vector“官方大小”);相反,operator[]几乎只做一个指针求和,并且很容易内联。也许使用迭代器(在vector的情况下基本上是指针)可以获得更多东西。
  • 有趣的是,唯一真正有力的竞争者 - resize d vector很简单malloc,它不提供花里胡哨,也不适合非POD类型(和仍然在这个非常测试中导致表现略差)。 vector 有效保持优势是件好事(我正在和你谈话,那些传播C式mallocs的人因为所谓的性能提升!)
  • std::list(用于比较)是所有这些中最慢的 - 但我们已经知道:)
显然是YMMV;首先,这些是特定体系结构的结果,具有特定的编译器及其特定的STL实现(即g ++ 4.4.5)。然后,我认为使用“复杂”类型(带有构造函数,复制构造函数等)可能会颠倒reserve / resize方法的位置(resize需要构造所有对象vector,而我认为reserve没有。更改使用的基元类型(int vs long vs short等)也可能会有一些更改。

此外,在所有测试之间,但是第一个和最后一个测试的区别很小;它肯定比标准偏差大得多,但我认为即使标准库中的微小变化也可能改变它们的一些相对结果。此外,改变元素的数量也应该有明显的效果,因为这些时间是不同的大O操作的总和,所以增加或减少元素的数量可以改变总和中的主要附录。

<小时/> 测试条件(无聊的东西)

AMD Phenom X4 955(3.2 GHz x4),4 GB RAM DDR3

编译器:

matteo@teoubuntu:~/cpp/vectorbenchmark2$ g++ --version
g++ (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

OS /内核:

matteo@teoubuntu:~/cpp/vectorbenchmark2$  uname -a
Linux teoubuntu 2.6.35-24-generic #42-Ubuntu SMP Thu Dec 2 02:41:37 UTC 2010 x86_64 GNU/Linux

编译选项:

matteo@teoubuntu:~/cpp/vectorbenchmark2$ make
g++ -O3 -Wall -Wextra -ansi -pedantic   -MMD -MF BenchmarkFunctors.o.d -c -o BenchmarkFunctors.o BenchmarkFunctors.cpp
g++ -O3 -Wall -Wextra -ansi -pedantic   -MMD -MF main.o.d -c -o main.o main.cpp
g++ -O3 -Wall -Wextra -ansi -pedantic   -MMD -MF Utils.o.d -c -o Utils.o Utils.cpp
g++ -O3 -Wall -Wextra -ansi -pedantic   BenchmarkFunctors.o main.o Utils.o -o vectorbenchmark2

Test suite code

答案 2 :(得分:0)

因为你想避免尽可能多的分配 - 事情就这么快。 :)

顺便说一下,向量动态分配空间这一事实并没有真正改变任何东西 - 事实上它加倍它的大小(或以其他方式改变它的常数因子,而不是将其增加一个常数值,这会使事情变得更有效率。如果您有兴趣,请查看complexity analysis


编辑:

示例:

如果您有1000个元素并且必须添加另一个元素,则不希望仅将数组增大1,因为在您添加另一个元素之后,您必须调整大小它再次。但是,如果您将其大小加倍,则在必须分配和复制 n 项目之前,它将执行 n 操作,因此,对于每个项目,成本为1。 (有关详细信息,请参阅链接。)

答案 3 :(得分:0)

通过为向量预先分配空间,我们需要确保通过将数据推入向量来不会超过那么多内存。 如果我们插入的数据多于可用的空闲内存块(因为向量在连续的内存中进行了分配),那么在向量中推送到目前为止的任何数据都会被复制并分配一个新的内存块,这会降低您的性能,如果有的话迭代器指向你的向量变得无效,因为数据已经分配了一个新的内存位置。

答案 4 :(得分:0)

您的问题含糊其辞,但如果您在知道需要多少元素时,在std::vector预留足够空间的优化,那么这是因为std::vector否则在逐个添加元素时多次调整大小。

当你预留空间时,你告诉它你知道它最终需要多少空间而且不必继续猜测。这避免了重复的分配和复制。

这通常并不重要,但是当你拥有一个非常大的矢量并且你事先知道它有多大时,肯定是一个考虑因素:你为什么不为它预留足够的空间? / p>