[英]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.