- mongodb - 在 MongoDB mapreduce 中,如何展平值对象?
- javascript - 对象传播与 Object.assign
- html - 输入类型 ="submit"Vs 按钮标签它们可以互换吗?
- sql - 使用 MongoDB 而不是 MS SQL Server 的优缺点
我最近正在阅读 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()
的第一个版本,因为我不太担心性能。但我想确定的是,这是否是一种我可以依靠编译器来优化的构造?
更新
感谢您对如何使这个无用的代码变得更好的建议。请注意,我的问题是关于编译器,而不是编程技术。目前唯一相关的答案来自 NPE和 Ellioh .
最佳答案
UPD:除非我弄错了,否则您所说的这本书已于 1999 年出版。那是 14 年前的事了,在现代编程中,14 年是很多时间。许多在 1999 年很好且可靠的建议现在可能已经完全过时了。虽然我的回答是关于单一编译器和单一平台,但也有一个更笼统的想法。
关心额外的变量、重用琐碎方法的返回值以及旧 C++ 的类似技巧是向 1990 年代 C++ 的退步。像 end()
这样的简单方法应该被很好地内联,并且内联的结果应该作为调用它的代码的一部分进行优化。 99% 的情况根本不需要手动操作,例如创建 end
变量。只有在以下情况下才应该这样做:
我查看了 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++
,你可以试试你的编译器)并反汇编 main
和 main1
:
对于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/
Cont r a type 代表一个需要延续的函数 a->r并产生 r 类型的结果。所以延续和整个 Cont r a产生相同类型的结果 r 。 我的问题是:两个结果是否必须相同值,或者可以是 Cont
嘿,我正在尝试查询在连续列中具有最大值的单词。例如:如果单词“test1”在 cont 中的值为 5,而“test2”的值为 2,则“test1”将显示在第一个位置。明白了吗? 所以。我正在尝试这样做
想起我之前在此列表中加入了一个字段,用于方便提示管理员公司的产品列表是否有修改之类的状态字段,于是可以断定是加了此字段的原因。 首先,先看看我之前是如何写这个提示状态字段的,实体中加入
我在玩Cont描述的单子(monad)技巧here在 this SO question . 这个函数让你“跳回”到计算的早期,接受一个参数,这样你就可以做不同的事情: import Control.M
我在玩 CPS 和 Control.Monad.Cont并想知道我们通过注意一元结构获得了什么。对于这样的代码: sumOfSquares'cps :: Cont r Int -> Cont r In
我正在为 this question 提供答案我想到了使用 Cont monad 的想法。我对 Haskell 的了解不足以解释为什么这个程序不起作用 import Control.Monad.Con
我有一个任务:写一个函数evalCPS它评估由下面的 ADT 形式化的表达式,使用继续传递样式但没有 Cont Monad 或类似的东西。 data Expr a = Expr a :+: Expr
这就是 Cont monad 的定义方式: newtype Cont r a = Cont { runCont :: (a -> r) -> r } instance Monad (Cont r) w
我正在为 this question 提供答案我想到了使用 Cont monad 的想法。我对 Haskell 的了解不足以解释为什么这个程序不起作用 import Control.Monad.Con
我正在看一个带有 python-ptrace 的游戏。我不想与服务器断开连接,因此在附加到进程后,我立即调用 cont() 以允许它继续运行。 在这种状态下我仍然可以读取内存,但无法写入。 是否有办法
我在 Standard ML (SMLofNJ.Cont) 中阅读有关continuations的内容。我了解 callcc 和 throw 的作用,但无法理解 isolate 。文档说 Discar
我真的很难理解 callCC。我得到了延续的力量,我一直在我的一些项目中使用这个概念来创造很酷的概念。但是我从来不需要使用比 cont :: ((a->r)->r)-> Cont r a 更强大的东西
我正在尝试 Cont monad,并发现了以下问题。 首先构造一个无限列表并将所有元素提升到 Cont monad 使用序列操作在无限列表上获取 Cont monad。 例如,当我们尝试使用 head
我正在阅读this implementation Haskell 中的 Continuation,感觉比较棘手,很多时候,r 并不是那么重要,所以我们提供了reset,方便被替换。 所以我认为Cont
我正在用 Haskell 编写一个该死的解释器,我想出了一个我认为非常有趣的程序描述: data Program m = Instruction (m ()) (Program m)
我开始阅读The Mother of All Monads ,并输入这个例子: import Control.Monad.Cont ex1 = do a :l ContMonad.hs [1 o
我最近正在查看此页面: https://en.wikipedia.org/wiki/C_syntax#Iteration_statements有这样的代码片段: e1; while (e2) {
我最近正在学习 Herb Sutter 的“Exceptional C++”,我对他在第 6 项 - 临时对象中给出的特定建议深表怀疑。 他提出在以下代码中查找不必要的临时对象: string Fin
我遇到了一个问题。我们有一个干净的脚本用来清理旧文件,有时我们需要停止它,稍后再启动它。像下面的过程。我们在check.sh中使用kill -STOP $pid和kill -CONT $pid来控制c
我最近正在阅读 Herb Sutter 的“Exceptional C++”,我对他在第 6 项 - 临时对象中给出的特定建议表示严重怀疑。 他提出在以下代码中查找不必要的临时对象: string F
我是一名优秀的程序员,十分优秀!