簡體   English   中英

為什么vector(size)比new []慢?

[英]Why is vector(size) slower than new[]?

我正在對一些 STL 算法進行基准測試,我對以下代碼所花費的時間感到驚訝:(我用time命令測量了 g++ 編譯代碼 [無優化])

#include <vector>
struct vec2{
    int x, y;
    vec2():x(0), y(0) {}
};
int main(int argc, char* argv[]){
    const int size = 200000000;
    std::vector<vec2> tab(size); //2.26s
//  vec2* tab = new vec2[size]; //1.29s
//  tab[0].x = 0;
//  delete[] tab;
    return 0;
}

向量初始化所用的時間是 2.26 秒,而new的(和delete )需要 1.29 秒。 向量 ctor 在做什么會花費更長的時間? new[]在每個元素上調用構造函數,就像vector ctor 那樣,對吧?

然后我用 -O3 編譯,一切都快了,但兩個代碼之間仍然存在差距。 (我分別得到了 0.83s 和 0.75s)

有任何想法嗎?

速度將取決於實現,但向量較慢的最可能原因是向量無法默認構造其元素。 向量元素始終是復制構造的。 例如

std::vector<vec2> tab(size);

實際上被解釋為

std::vector<vec2> tab(size, vec2());

即第二個參數從默認參數中獲取它的值。 然后向量分配原始 memory 並將這個從外部傳遞的默認構造元素復制到新向量的每個元素中(通過使用復制構造函數)。 這通常比直接默認構造每個元素(如new[]所做的那樣)要慢。

用一個代碼草圖來說明區別, new vec2[size]大致相當於

vec2 *v = (vec2 *) malloc(size * sizeof(vec2));

for (size_t i = 0; i < size; ++i)
  // Default-construct `v[i]` in place
  new (&v[i]) vec2();

return v;

vector<vec2>(size)大致相當於

vec2 source; // Default-constructed "original" element

vec2 *v = (vec2 *) malloc(size * sizeof(vec2));

for (size_t i = 0; i < size; ++i)
  // Copy-construct `v[i]` in place
  new (&v[i]) vec2(source);

return v;

根據實施情況,第二種方法可能會變得更慢。

雖然速度上的兩倍差異很難證明是合理的,但是對未優化代碼進行基准測試也沒有任何意義。 您在優化代碼中觀察到的不那么顯着的差異正是在這種情況下人們可能合理預期的結果。

兩個版本都初始化 memory。

正如一些人指出的那樣,向量使用復制構造,而數組使用默認構造函數。 您的編譯器似乎比前者更好地優化了后者。

請注意,在現實生活中,您很少希望一舉初始化如此龐大的數組。 (一堆零有什么用?顯然你打算最終把其他東西放在那里......並且初始化數百兆字節對緩存非常不友好。)

相反,您會編寫如下內容:

const int size = 200000000;
std::vector<vec2> v;
v.reserve(size);

然后,當您准備將真實元素放入向量中時,您可以使用v.push_back(element) reserve()分配 memory 而不初始化它; push_back()復制構造到保留空間中。

或者,當您想將新元素放入向量中時,可以使用v.resize(v.size()+1) ,然后修改元素v.back() (這就是“池分配器”的工作方式。)雖然這個序列會初始化元素然后覆蓋它,但它都會發生在 L1 緩存中,這幾乎與根本不初始化它一樣快。

因此,為了公平比較,請嘗試使用大向量(帶有reserve )與數組來創建一系列不同的項目。 您應該會發現向量更快。

在分析了 VC++ 為這兩種情況生成的程序集之后,我發現了以下內容。 編譯器幾乎內聯了所有內容,並在 memory 分配后生成了非常相似的初始化循環。 在矢量內循環的情況下,如下所示:

013E3FC0  test        eax,eax  
013E3FC2  je          std::_Uninit_def_fill_n<vec2 *,unsigned int,vec2,std::allocator<vec2>,vec2>+19h (13E3FC9h)  
013E3FC4  mov         dword ptr [eax],edx  
013E3FC6  mov         dword ptr [eax+4],esi  
013E3FC9  add         eax,8  
013E3FCC  dec         ecx  
013E3FCD  jne         std::_Uninit_def_fill_n<vec2 *,unsigned int,vec2,std::allocator<vec2>,vec2>+10h (13E3FC0h)  

其中edxesi寄存器在循環之外被歸零:

00013FB5  xor         edx,edx  
00013FB7  xor         esi,esi  
00013FB9  lea         esp,[esp]  

如果new[]內部循環如下所示:

009F1800  mov         dword ptr [ecx],0  
009F1806  mov         dword ptr [ecx+4],0  
009F180D  add         ecx,8  
009F1810  dec         edx  
009F1811  jns         main+30h (9F1800h)  

差異非常微不足道,在vector的情況下還有一些指令,但也可能來自寄存器的mov更快。 由於在大多數實際情況下,構造函數所做的不僅僅是分配零,因此這種差異幾乎不會被注意到。 所以這個測試的價值是值得懷疑的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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