gpt4 book ai didi

c++ - pIter != cont.end() 在 for 循环中的性能

转载 作者:IT老高 更新时间:2023-10-28 21:46:37 26 4
gpt4 key购买 nike

我最近正在阅读 Herb Sutter 的“Exceptional C++”,我对他在第 6 项 - 临时对象中给出的特定建议表示严重怀疑。

他提出在以下代码中查找不必要的临时对象:

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 "";
}

作为示例之一,他建议在循环之前预先计算 emps.end() 的值,因为每次迭代都会创建一个临时对象:

For most containers (including list), calling end() returns a temporary object that must be constructed and destroyed. Because the value will not change, recomputing (and reconstructing and redestroying) it on every loop iteration is both needlessly inefficient and unaesthetic. The value should be computed only once, stored in a local object, and reused.

他建议替换为以下内容:

list<Employee>::const_iterator end(emps.end());
for (list<Employee>::const_iterator i = emps.begin(); i != end; ++i)

对我来说,这是不必要的复杂化。即使用紧凑的 auto 替换丑陋的类型声明,他仍然得到两行代码而不是一行。更重要的是,他在外部作用域中有这个 end 变量。

我确信现代编译器无论如何都会优化这段代码,因为我实际上在这里使用 const_iterator 并且很容易检查循环内容是否以某种方式访问​​容器。编译器在过去 13 年里变得更聪明了,对吧?

无论如何,在大多数情况下,我会更喜欢带有 i != emps.end() 的第一个版本,因为我不太担心性能。但我想确定的是,这是否是一种我可以依靠编译器来优化的构造?

更新

感谢您对如何使这个无用的代码变得更好的建议。请注意,我的问题是关于编译器,而不是编程技术。目前唯一相关的答案来自 NPEEllioh .

最佳答案

UPD:除非我弄错了,否则您所说的这本书已于 1999 年出版。那是 14 年前的事了,在现代编程中,14 年是很多时间。许多在 1999 年很好且可靠的建议现在可能已经完全过时了。虽然我的回答是关于单一编译器和单一平台,但也有一个更笼统的想法。

关心额外的变量、重用琐碎方法的返回值以及旧 C++ 的类似技巧是向 1990 年代 C++ 的退步。像 end() 这样的简单方法应该被很好地内联,并且内联的结果应该作为调用它的代码的一部分进行优化。 99% 的情况根本不需要手动操作,例如创建 end 变量。只有在以下情况下才应该这样做:

  1. 您知道,您应该在某些编译器/平台上运行的代码没有得到很好的优化。
  2. 它已成为您程序中的瓶颈(“避免过早优化”)。

我查看了 64 位 g++ 生成的内容:

gcc version 4.6.3 20120918 (prerelease) (Ubuntu/Linaro 4.6.3-10ubuntu1)

最初我认为优化它应该没问题,两个版本之间应该没有区别。但看起来事情很奇怪:您认为不是最佳的版本实际上更好。我认为,道德是:没有理由比编译器更聪明。让我们看看这两个版本。

#include <list>

using namespace std;

int main() {
list<char> l;
l.push_back('a');

for(list<char>::iterator i=l.begin(); i != l.end(); i++)
;

return 0;
}

int main1() {
list<char> l;
l.push_back('a');
list<char>::iterator e=l.end();
for(list<char>::iterator i=l.begin(); i != e; i++)
;

return 0;
}

然后我们应该对它进行优化编译(我使用 64 位 g++,你可以试试你的编译器)并反汇编 mainmain1:

对于main:

(gdb) disas main
Dump of assembler code for function main():
0x0000000000400650 <+0>: push %rbx
0x0000000000400651 <+1>: mov $0x18,%edi
0x0000000000400656 <+6>: sub $0x20,%rsp
0x000000000040065a <+10>: lea 0x10(%rsp),%rbx
0x000000000040065f <+15>: mov %rbx,0x10(%rsp)
0x0000000000400664 <+20>: mov %rbx,0x18(%rsp)
0x0000000000400669 <+25>: callq 0x400630 <_Znwm@plt>
0x000000000040066e <+30>: cmp $0xfffffffffffffff0,%rax
0x0000000000400672 <+34>: je 0x400678 <main()+40>
0x0000000000400674 <+36>: movb $0x61,0x10(%rax)
0x0000000000400678 <+40>: mov %rax,%rdi
0x000000000040067b <+43>: mov %rbx,%rsi
0x000000000040067e <+46>: callq 0x400610 <_ZNSt8__detail15_List_node_base7_M_hookEPS0_@plt>
0x0000000000400683 <+51>: mov 0x10(%rsp),%rax
0x0000000000400688 <+56>: cmp %rbx,%rax
0x000000000040068b <+59>: je 0x400698 <main()+72>
0x000000000040068d <+61>: nopl (%rax)
0x0000000000400690 <+64>: mov (%rax),%rax
0x0000000000400693 <+67>: cmp %rbx,%rax
0x0000000000400696 <+70>: jne 0x400690 <main()+64>
0x0000000000400698 <+72>: mov %rbx,%rdi
0x000000000040069b <+75>: callq 0x400840 <std::list<char, std::allocator<char> >::~list()>
0x00000000004006a0 <+80>: add $0x20,%rsp
0x00000000004006a4 <+84>: xor %eax,%eax
0x00000000004006a6 <+86>: pop %rbx
0x00000000004006a7 <+87>: retq

查看位于 0x0000000000400683-0x000000000040068b 的命令。这就是循环体,它似乎已经完美优化:

   0x0000000000400690 <+64>:    mov    (%rax),%rax
0x0000000000400693 <+67>: cmp %rbx,%rax
0x0000000000400696 <+70>: jne 0x400690 <main()+64>

对于main1:

(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>

但是循环周围有很多额外的东西。显然,额外的代码让事情变得更糟了。

关于c++ - pIter != cont.end() 在 for 循环中的性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15433381/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com