[英]How does std::vector reallocates every time an item is inserted using a loop?
我讀過std::vector
重新分配的工作原理是這樣的:
重新分配有多貴? 它包括四個步驟:
- 為所需的新容量分配足夠的內存;
- 將舊內存中的元素復制到新內存中;
- 銷毀舊記憶中的元素; 和
- 釋放舊內存。
因此,像這樣使用.reserve()
可能是一個好習慣:
std::vector<int> vec;
int unnkownNumberOfElementsToAdd = 30; //it's 30 now, but suppose you don't know
vec.reserve(unnkownNumberOfElementsToAdd);
for(int i=0; i<unnkownNumberOfElementsToAdd; i++ )
{
vec.push_back(i);
}
這樣它就不會在每次插入項目時重新分配整個向量。 但有趣的是,如果您不.reserve()
並且每次插入i
時都打印vec.size
和vec.capacity
,則輸出如下:
size | capacity
1 1
2 2
3 3
4 4
5 6
6 6
7 9
8 9
9 9
10 13
11 13
12 13
13 13
14 19
15 19
16 19
17 19
18 19
19 19
20 28
21 28
22 28
23 28
24 28
25 28
26 28
27 28
28 28
29 42
30 42
我不知道容量增加是否取決於編譯器(我使用的是舊的 VS2003)。 如果不是,這種重新分配是如何工作的?
std::vector
不會在每次添加元素時重新分配。 它在容量用完時重新分配。 當它重新分配時,它不會再為 1 個元素分配空間。 它通常按當前容量的某個因素進行分配。 我相信 VS 使用 1.5 的因子,而其他一些使用 2。它必須這樣做以確保push_back
具有攤銷 O(1) 復雜性,這是標准的要求。
如果您確切地知道要在其生命周期內向矢量添加多少元素,那么保留它仍然是一個好主意,imo。 有些人可能會考慮過早的優化。 但做起來就是這么簡單,我認為不做是過早的悲觀。
通常,每次當前大小超過最大容量時,容量都會增加一個常數因子。 但它依賴於編譯器,特別是在較小的數字中。 初始容量也是如此。 例如g++
將保留 8 個元素 AFAIK 的初始大小。
將存儲乘以一個因子會導致插入的持續攤銷復雜度,但在插入的有效工作和未使用的容量之間存在權衡。 對於大向量,保留但未使用的容量可能很大(直到使用的常數因子)。 這就是為什么這是編譯器定義的行為。 針對內存受限的系統(例如嵌入式系統)進行優化的編譯器可能會選擇減少這個因素,但代價是由於更多的分配而增加了運行時間。
如果您知道大小,只需使用reserve
以避免依賴於編譯器的特定行為。
reserve()
現在不知何故已經過時了,因為向量重新分配策略已經為大多數使用量身定做。
但是,它們也有缺點。 例如,當我處理 32 位系統時,我在內存中有一個非常大的數據結構(很大,因為它包含很多元素),其中一個元素的成員是向量。 向量非常短——其中許多是空的,有些只有一兩個元素。 然而,實現預分配了 32 個元素,並導致我的程序內存不足(僅僅是因為這些向量的數量太多了)。 用 list 替換 vector 降低了我的隨機訪問,但允許程序運行。
使用reserve
時要非常小心。 如果你在循環中錯誤地使用它,你會因為強制重新分配是算術而不是幾何而大大增加你的執行時間:
#include <chrono>
#include <iostream>
#include <vector>
void Add2Items(std::vector<int>& vec, bool reserve)
{
if(reserve)
{
// Surely reserving upfront the sapce for the 2 items I'm going to add is a _good thing_
vec.reserve(vec.size() + 2);
}
vec.push_back(42);
vec.push_back(42);
}
int main()
{
for(int iter=0; iter<2; ++iter)
{
bool reserve = iter ? false : true;
auto begin = std::chrono::system_clock::now();
std::vector<int> vec;
for(int i=0; i<1024 * 64; ++i)
{
Add2Items(vec, reserve);
}
auto end = std::chrono::system_clock::now();
std::cout << "Iteration " << iter << " with " << (reserve ? "reserve" : "no reserve") << " took " << std::chrono::duration_cast<std::chrono::milliseconds>(end-begin).count() << "ms" << std::endl;
}
}
輸出
Iteration 0 with reserve took 2432ms
Iteration 1 with no reserve took 133ms
這是了解向量如何自動分配的好方法。 當然,這樣做是為了提高效率,並且輸出會因系統而異,但它會給你一個大致的概念。
#include <iostream>
#include <vector>
int main(void)
{
std::vector<int> v;
int i = 0;
while (v.size() <= 100)
{
std::cout << "Size: " << v.size() << std::endl;
std::cout << "Capacity: " << v.capacity() << std::endl;
v.push_back(i++);
}
}
它通常會打印如下內容:
Size: 0
Capacity: 0
Size: 1
Capacity: 1
Size: 2
Capacity: 2
Size: 3
Capacity: 4
Size: 4
Capacity: 4
Size: 5
Capacity: 8
Size: 6
Capacity: 8
Size: 7
Capacity: 8
Size: 8
Capacity: 8
Size: 9
Capacity: 16
Size: 10
Capacity: 16
Size: 11
Capacity: 16
Size: 12
Capacity: 16
Size: 13
Capacity: 16
Size: 14
Capacity: 16
Size: 15
Capacity: 16
Size: 16
Capacity: 16
Size: 17
Capacity: 32
Size: 18
Capacity: 32
...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.