具有预定义容量的C ++向量性能

时间:2016-06-06 11:52:57

标签: c++ memory-management vector performance-testing

有两种方法可以定义std :: vector(我知道):

std::vector<int> vectorOne;

std::vector<int> vectorTwo(300);

因此,如果我没有定义第一个并用300个int填充它,那么它必须重新分配内存来存储这些内容。这意味着它不会是例如地址0x0到0x300,但可能会在中间分配内存,因为它必须在之后重新分配,但第二个矢量已经为它们保留了那些地址,因此中间没有空间。

这是否会影响性能,我怎么能这样做呢?

3 个答案:

答案 0 :(得分:3)

保证

std::vector始终将其数据存储在连续的内存块中。这意味着当您添加项目时,它必须尝试增加其使用的内存范围。如果向量后面的内存中有其他内容,则需要在内存中的其他位置找到正确大小的空闲块,并将所有旧数据+新数据复制到其中。 就时间而言,这是一项相当昂贵的操作,因此它会尝试通过分配比您需要的块稍大的块来缓解。这允许您在整个重新分配和移动操作发生之前添加多个项目。

Vector有两个属性:sizecapacity。前者是实际拥有多少元素,后者是总共保留了多少个元素。例如,如果您有一个带有size() == 10capacity() == 18的向量,则意味着您可以在需要重新分配之前再添加8个元素。

容量如何以及何时准确增加,取决于STL版本的实施者。您可以使用以下测试来测试计算机上发生的情况:

#include <iostream>
#include <vector>

int main() {
    using std::cout;
    using std::vector;

    // Create a vector with values 1 .. 10
    vector<int> v(10);
    std::cout << "v has size " << v.size() << " and capacity " << v.capacity() << "\n";

    // Now add 90 values, and print the size and capacity after each insert
    for(int i = 11; i <= 100; ++i)
    {
        v.push_back(i);
        std::cout << "v has size " << v.size() << " and capacity " << v.capacity() 
            << ". Memory range: " << &v.front() << " -- " << &v.back() << "\n"; 
    }

    return 0;
}

我在IDEOne上运行它并得到以下输出:

v has size 10 and capacity 10
v has size 11 and capacity 20. Memory range: 0x9899a40 -- 0x9899a68
v has size 12 and capacity 20. Memory range: 0x9899a40 -- 0x9899a6c
v has size 13 and capacity 20. Memory range: 0x9899a40 -- 0x9899a70
...
v has size 20 and capacity 20. Memory range: 0x9899a40 -- 0x9899a8c
v has size 21 and capacity 40. Memory range: 0x9899a98 -- 0x9899ae8
...
v has size 40 and capacity 40. Memory range: 0x9899a98 -- 0x9899b34
v has size 41 and capacity 80. Memory range: 0x9899b40 -- 0x9899be0

您可以看到容量增加和重新分配正在发生,并且您还看到此特定编译器选择在每次达到限制时将容量加倍。

在某些系统上,算法会更加细微,随着插入更多项目而增长得更快(因此,如果向量较小,则会浪费很少的空间,但如果注意到您在其中插入了大量项目,则会分配更多内容避免不得不经常增加容量。)

PS:请注意设置矢量的sizecapacity之间的区别。

vector<int> v(10);

将创建一个capacity至少为10且size() == 10的向量。如果您打印v的内容,您会看到它包含

0 0 0 0 0 0 0 0 0 0

10个整数及其默认值。您推入的下一个元素可能(并且可能)导致重新分配。另一方面,

vector<int> v();
v.reserve(10);

将创建一个向量,但其初始容量设置为10而不是默认值(可能为1)。您可以确定您推入的前10个元素不会导致分配(而可能将会但不一定,因为reserve可能实际上将容量设置为 比您要求的更多。

答案 1 :(得分:1)

你应该使用reserve()方法:

std::vector<int> vec;
vec.reserve(300);
assert(vec.size() == 0); // But memory is allocated

这解决了这个问题。

在您的示例中,它会极大地影响性能。你可以预料,当你向量溢出时,它会使分配的内存加倍。因此,如果将push_back()转换为向量N次(并且您没有调用“reserve()”),则可以预期O(logN)重新分配,每次重新分配都会导致复制所有值。因此,总复杂度预计为O(N * logN),尽管C ++标准没有规定。

答案 2 :(得分:0)

差异可能很大,因为如果数据在内存中不相邻,则可能必须从主内存中提取数据,这比l1缓存提取慢200倍。这不会发生在矢量中,因为矢量中的数据必须是相邻的。

请参阅https://www.youtube.com/watch?v=YQs6IC-vgmo

使用std :: vector :: reserve,可以避免重新分配事件。 C ++&#39; chrono&#39;标题具有良好的时间效用,用于测量时间差,以高分辨率标记。