[英]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)
其中edx
和esi
寄存器在循環之外被歸零:
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.