[英]Why access in a for loop is faster than access in a ranged-for in -O0 but not in -O3?
我正在學習C ++(和C ++ 11)的性能。 我需要在“調試”和“發布”模式下進行性能測試,因為我花時間在調試和執行上。
我對這兩個測試以及不同的編譯器標志優化有多少變化感到驚訝。
測試迭代器1:
測試迭代器2:
PD:我使用以下時鍾代碼 。
測試迭代器1:
void test_iterator_1()
{
int z = 0;
int nv = 1200000000;
std::vector<int> v(nv);
size_t count = v.size();
for (unsigned int i = 0; i < count; ++i) {
v[i] = 1;
}
}
測試迭代器2:
void test_iterator_2()
{
int z = 0;
int nv = 1200000000;
std::vector<int> v(nv);
for (int& i : v) {
i = 1;
}
}
更新 :問題仍然是相同的,但對於-O3中的定距來說,差異很小。 因此,對於循環1來說是最好的 。
更新2 :結果:
使用-O3:
t1: 80 units
t2: 74 units
使用-O0:
t1: 287 units
t2: 538 units
更新3: 代碼 ! 。 編譯為: g ++ -std = c ++ 11 test.cpp -O0 (然后是-O3)
您的第一個測試實際上是將向量中每個元素的值設置為1。
您的第二個測試是將向量中每個元素的副本的值設置為1(原始向量是相同的)。
當您進行優化時,第二個循環很有可能被完全刪除,因為它基本上什么都不做。
如果要第二個循環實際設置值:
for (int& i : v) // notice the &
{
i = 1;
}
進行更改后,循環可能會產生幾乎相同的匯編代碼。
附帶說明一下,如果要將整個向量初始化為單個值,則更好的方法是:
std::vector<int> v(SIZE, 1);
編輯
程序集相當長(超過100行),因此我不會全部發布,但需要注意以下幾點:
版本1將存儲一個用於count
和遞增i
的值,並每次對其進行測試。 版本2使用迭代器(基本上與std::for_each(b.begin(), v.end() ...)
)。 因此,用於循環維護的代碼非常不同(版本2的設置更多,但每次迭代的工作量更少)。
版本1(只是循環的內容)
mov eax, DWORD PTR _i$2[ebp]
push eax
lea ecx, DWORD PTR _v$[ebp]
call ??A?$vector@HV?$allocator@H@std@@@std@@QAEAAHI@Z ; std::vector<int,std::allocator<int> >::operator[]
mov DWORD PTR [eax], 1
第2版(只是循環的內容)
mov eax, DWORD PTR _i$2[ebp]
mov DWORD PTR [eax], 1
當它們得到優化時,所有這些都會改變(除了一些指令的順序),輸出幾乎是相同的。
版本1(優化)
push ebp
mov ebp, esp
sub esp, 12 ; 0000000cH
push ecx
lea ecx, DWORD PTR _v$[ebp]
mov DWORD PTR _v$[ebp], 0
mov DWORD PTR _v$[ebp+4], 0
mov DWORD PTR _v$[ebp+8], 0
call ?resize@?$vector@HV?$allocator@H@std@@@std@@QAEXI@Z ; std::vector<int,std::allocator<int> >::resize
mov ecx, DWORD PTR _v$[ebp+4]
mov edx, DWORD PTR _v$[ebp]
sub ecx, edx
sar ecx, 2 ; this is the only differing instruction
test ecx, ecx
je SHORT $LN3@test_itera
push edi
mov eax, 1
mov edi, edx
rep stosd
pop edi
$LN3@test_itera:
test edx, edx
je SHORT $LN21@test_itera
push edx
call DWORD PTR __imp_??3@YAXPAX@Z
add esp, 4
$LN21@test_itera:
mov esp, ebp
pop ebp
ret 0
版本2(優化)
push ebp
mov ebp, esp
sub esp, 12 ; 0000000cH
push ecx
lea ecx, DWORD PTR _v$[ebp]
mov DWORD PTR _v$[ebp], 0
mov DWORD PTR _v$[ebp+4], 0
mov DWORD PTR _v$[ebp+8], 0
call ?resize@?$vector@HV?$allocator@H@std@@@std@@QAEXI@Z ; std::vector<int,std::allocator<int> >::resize
mov edx, DWORD PTR _v$[ebp]
mov ecx, DWORD PTR _v$[ebp+4]
mov eax, edx
cmp edx, ecx
je SHORT $LN1@test_itera
$LL33@test_itera:
mov DWORD PTR [eax], 1
add eax, 4
cmp eax, ecx
jne SHORT $LL33@test_itera
$LN1@test_itera:
test edx, edx
je SHORT $LN47@test_itera
push edx
call DWORD PTR __imp_??3@YAXPAX@Z
add esp, 4
$LN47@test_itera:
mov esp, ebp
pop ebp
ret 0
不必擔心每次操作需要花費多少時間,而唐納德·克努斯(Donald Knuth) 的所有邪惡言論都歸因於過早的優化 。 寫通俗易懂,簡單的程序,您的時間,同時編寫程序(和閱讀下周調整它,或者找出為什么&%$#它給瘋狂的結果),比浪費任何一台計算機的時間更有價值。 只需將您的每周收入與一台現成機器的價格進行比較,然后考慮節省多少時間即可節省幾分鍾的計算時間。
當測量結果表明性能不足時,請不要擔心。 然后,您必須衡量運行時(或內存,或其他任何重要資源)在何處花費,並查看如何使其更好。 喬恩·本特利(Jon Bentley)的這本書(絕版)(非常絕版)(其中大部分也出現在他的“編程珍珠”中)令人大開眼界,對於任何嶄露頭角的程序員都必須閱讀。
優化是模式匹配:編譯器可以識別和優化許多不同的情況。 如果您以使編譯器無法識別模式的方式更改代碼,則優化的效果突然消失。
因此,您所看到的只是在不進行優化的情況下,范圍for循環會產生更多膨脹的代碼,但這種形式的優化器能夠識別出它在沒有迭代器的情況下無法識別的模式。
無論如何,如果您好奇的話,應該看一下生成的匯編代碼(使用-S
選項進行編譯)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.