C++ pIter的性能!=for循环中的cont.end()
最近我通过了Herb Sutter的“Exception C++”,我对他在第6项“临时对象”中给出的一个特别建议表示严重怀疑 他提出在以下代码中查找不必要的临时对象:C++ pIter的性能!=for循环中的cont.end(),c++,performance,stl,compiler-optimization,temporary-objects,C++,Performance,Stl,Compiler Optimization,Temporary Objects,最近我通过了Herb Sutter的“Exception C++”,我对他在第6项“临时对象”中给出的一个特别建议表示严重怀疑 他提出在以下代码中查找不必要的临时对象: string FindAddr(list<Employee> emps, string name) { for (list<Employee>::iterator i = emps.begin(); i != emps.end(); i++) { if( *i == name )
string FindAddr(list<Employee> emps, string name)
{
for (list<Employee>::iterator i = emps.begin(); i != emps.end(); i++)
{
if( *i == name )
{
return i->addr;
}
}
return "";
}
string FindAddr(list<Employee> emps, string name)
{
auto end = emps.end();
for (list<Employee>::iterator i = emps.begin(); i != end; i++)
{
if( *i == name )
{
return i->addr;
}
}
return "";
}
对我来说,这是不必要的复杂化。即使一个人用compactauto
替换丑陋的类型声明,他仍然会得到两行代码而不是一行。更重要的是,他在外部范围中有这个end
变量
我确信现代编译器无论如何都会优化这段代码,因为我实际上在这里使用const_迭代器
,并且很容易检查循环内容是否以某种方式访问容器。编译器在过去的13年里变得更聪明了,对吗
无论如何,我更喜欢带有I!=emps.end()
在大多数情况下,我并不太担心性能。但我想确定的是,这是否是一种我可以依靠编译器来优化的构造
更新
感谢您对如何改进这段无用代码的建议。请注意,我的问题是关于编译器,而不是编程技术。目前唯一相关的答案是from和。像vector returns variable这样的容器,它在
end()
调用上存储指向末尾的指针,并进行了优化。如果您在“代码>结尾”()/代码>中编写了一些查找等容器,请调用考虑写入< /p>
for (list<Employee>::const_iterator i = emps.begin(), end = emps.end(); i != end; ++i)
{
...
}
for(list::const_迭代器i=emps.begin(),end=emps.end();i!=end;++i)
{
...
}
对于速度容器,如vector returns variable,它在
end()
call上存储指向末尾的指针,并进行了优化。如果您在“代码>结尾”()/代码>中编写了一些查找等容器,请调用考虑写入< /p>
for (list<Employee>::const_iterator i = emps.begin(), end = emps.end(); i != end; ++i)
{
...
}
for(list::const_迭代器i=emps.begin(),end=emps.end();i!=end;++i)
{
...
}
对于速度有几件事。。。首先,一般来说,构建迭代器(在发布模式下,未经检查的分配器)的成本是最低的。它们通常是指针周围的包装。使用选中的分配器(VS中的默认值),您可能会有一些成本,但如果您确实需要性能,请在使用未选中的分配器测试重建之后进行测试 代码不必像您发布的那样难看:
for (list<Employee>::const_iterator it=emps.begin(), end=emps.end();
it != end; ++it )
for(list::const_迭代器it=emps.begin(),end=emps.end();
它!=结束;++它)
关于是否要使用一种方法或其他方法的主要决定应该是应用于容器的操作。如果容器可能正在更改其大小,那么您可能希望在每次迭代中重新计算
end
迭代器。如果没有,您可以只预计算一次,然后像上面的代码一样重用。有几件事。。。首先,一般来说,构建迭代器(在发布模式下,未经检查的分配器)的成本是最低的。它们通常是指针周围的包装。使用选中的分配器(VS中的默认值),您可能会有一些成本,但如果您确实需要性能,请在使用未选中的分配器测试重建之后进行测试
代码不必像您发布的那样难看:
for (list<Employee>::const_iterator it=emps.begin(), end=emps.end();
it != end; ++it )
for(list::const_迭代器it=emps.begin(),end=emps.end();
它!=结束;++它)
关于是否要使用一种方法或其他方法的主要决定应该是应用于容器的操作。如果容器可能正在更改其大小,那么您可能希望在每次迭代中重新计算
end
迭代器。如果没有,您可以只预计算一次,然后重复使用上面的代码。如果您真的需要性能,您可以让全新的C++11编译器为您编写:
for (const auto &i : emps) {
/* ... */
}
是的,这是在开玩笑。赫伯的例子现在已经过时了。但是,由于您的编译器还不支持它,让我们进入真正的问题:
这是一种我可以依靠编译器来优化的结构吗
我的经验是,编译器编写者比我聪明得多。我不能依靠编译器来优化任何一段代码,因为它可能会选择优化其他影响更大的代码。唯一确定的方法是在系统上的编译器上尝试这两种方法,看看会发生什么。检查您的分析器结果。如果对
.end()
的调用仍然存在,请将其保存在单独的变量中。否则,就不用担心了。如果您真的需要性能,您可以让全新的C++11编译器为您编写:
for (const auto &i : emps) {
/* ... */
}
是的,这是在开玩笑。赫伯的例子现在已经过时了。但是,由于您的编译器还不支持它,让我们进入真正的问题:
这是一种我可以依靠编译器来优化的结构吗
我的经验是,编译器编写者比我聪明得多。我不能依靠编译器来优化任何一段代码,因为它可能会选择优化其他影响更大的代码。唯一确定的方法是在系统上的编译器上尝试这两种方法,看看会发生什么。检查您的分析器结果。如果对
.end()
的调用仍然存在,请将其保存在单独的变量中。否则,不用担心。我使用g++4.7.2
和-O3-std=c++11
编译了以下稍微有点粗糙的代码,并为两个函数获得了相同的程序集:
#include <list>
#include <string>
using namespace std;
struct Employee: public string { string addr; };
string FindAddr1(list<Employee> emps, string name)
{
for (list<Employee>::const_iterator i = emps.begin(); i != emps.end(); i++)
{
if( *i == name )
{
return i->addr;
}
}
return "";
}
string FindAddr2(list<Employee> emps, string name)
{
list<Employee>::const_iterator end(emps.end());
for (list<Employee>::const_iterator i = emps.begin(); i != end; i++)
{
if( *i == name )
{
return i->addr;
}
}
return "";
}
#包括
#包括
使用名称空间std;
结构Employee:public字符串{string addr;};
字符串FindAddr1(列出EMP、字符串名称)
{
for(list::const_迭代器i=emps.begin();i!=emps.end();i++)
{
如果(*i==名称)
{
返回i->addr;
}
}
返回“”;
}
字符串FindAddr2(列出EMP、字符串名称)
{
list::const_迭代器end(emps.end());
对于(列表::常量)_
0x0000000000400690 <+64>: mov (%rax),%rax
0x0000000000400693 <+67>: cmp %rbx,%rax
0x0000000000400696 <+70>: jne 0x400690 <main()+64>
(gdb) disas main1
Dump of assembler code for function main1():
0x00000000004007b0 <+0>: push %rbp
0x00000000004007b1 <+1>: mov $0x18,%edi
0x00000000004007b6 <+6>: push %rbx
0x00000000004007b7 <+7>: sub $0x18,%rsp
0x00000000004007bb <+11>: mov %rsp,%rbx
0x00000000004007be <+14>: mov %rsp,(%rsp)
0x00000000004007c2 <+18>: mov %rsp,0x8(%rsp)
0x00000000004007c7 <+23>: callq 0x400630 <_Znwm@plt>
0x00000000004007cc <+28>: cmp $0xfffffffffffffff0,%rax
0x00000000004007d0 <+32>: je 0x4007d6 <main1()+38>
0x00000000004007d2 <+34>: movb $0x61,0x10(%rax)
0x00000000004007d6 <+38>: mov %rax,%rdi
0x00000000004007d9 <+41>: mov %rsp,%rsi
0x00000000004007dc <+44>: callq 0x400610 <_ZNSt8__detail15_List_node_base7_M_hookEPS0_@plt>
0x00000000004007e1 <+49>: mov (%rsp),%rdi
0x00000000004007e5 <+53>: cmp %rbx,%rdi
0x00000000004007e8 <+56>: je 0x400818 <main1()+104>
0x00000000004007ea <+58>: mov %rdi,%rax
0x00000000004007ed <+61>: nopl (%rax)
0x00000000004007f0 <+64>: mov (%rax),%rax
0x00000000004007f3 <+67>: cmp %rbx,%rax
0x00000000004007f6 <+70>: jne 0x4007f0 <main1()+64>
0x00000000004007f8 <+72>: mov (%rdi),%rbp
0x00000000004007fb <+75>: callq 0x4005f0 <_ZdlPv@plt>
0x0000000000400800 <+80>: cmp %rbx,%rbp
0x0000000000400803 <+83>: je 0x400818 <main1()+104>
0x0000000000400805 <+85>: nopl (%rax)
0x0000000000400808 <+88>: mov %rbp,%rdi
0x000000000040080b <+91>: mov (%rdi),%rbp
0x000000000040080e <+94>: callq 0x4005f0 <_ZdlPv@plt>
0x0000000000400813 <+99>: cmp %rbx,%rbp
0x0000000000400816 <+102>: jne 0x400808 <main1()+88>
0x0000000000400818 <+104>: add $0x18,%rsp
0x000000000040081c <+108>: xor %eax,%eax
0x000000000040081e <+110>: pop %rbx
0x000000000040081f <+111>: pop %rbp
0x0000000000400820 <+112>: retq
0x00000000004007f0 <+64>: mov (%rax),%rax
0x00000000004007f3 <+67>: cmp %rbx,%rax
0x00000000004007f6 <+70>: jne 0x4007f0 <main1()+64>
string FindAddr(list<Employee> emps, string name)
{
auto end = emps.end();
for (list<Employee>::iterator i = emps.begin(); i != end; i++)
{
if( *i == name )
{
return i->addr;
}
}
return "";
}