簡體   English   中英

每次使用循環插入項目時,std::vector 如何重新分配?

[英]How does std::vector reallocates every time an item is inserted using a loop?

我讀過std::vector重新分配的工作原理是這樣的:

重新分配有多貴? 它包括四個步驟:

  1. 為所需的新容量分配足夠的內存;
  2. 將舊內存中的元素復制到新內存中;
  3. 銷毀舊記憶中的元素;
  4. 釋放舊內存。

因此,像這樣使用.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.sizevec.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM