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

C++ vector performance with predefined capacity

本文关键字:性能 C++ 预定义 容量      更新时间:2023-10-16

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

std::vector<int> vectorOne;

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

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

这会影响绩效吗?我怎么能衡量这一点?

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);

将创建具有至少10个capacitysize() == 10的向量。如果你打印v的内容,你会看到它包含

0 0 0 0 0 0 0 0 0 0

,即10个具有默认值的整数。您推入其中的下一个元素可能(很可能)导致重新分配。另一方面,

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

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

您应该使用reserve()方法:

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

这就解决了问题。

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

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

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

尽可能使用std::vector::reserve来避免realloc事件。C++的"chrono"标头具有良好的时间实用程序,可以在高分辨率刻度中测量时差。