for() 和 while() 之间有啥性能差异吗?
Posted
技术标签:
【中文标题】for() 和 while() 之间有啥性能差异吗?【英文标题】:Is there any performance difference between for() and while()?for() 和 while() 之间有什么性能差异吗? 【发布时间】:2010-10-25 06:36:15 【问题描述】:还是全部与语义有关?
【问题讨论】:
@Neil:因为可能有一些模糊的案例确实会产生真正的影响,而且一些小型基准测试不会测试这些案例。 @Richie - 因此,与其获得一些客观数据,不如对一些 SO 用户进行一次民意调查,他们都在使用诸如“可能”和“可能”之类的狡猾词?如果我真的关心答案,我不会采取这种方法。 在编写基准来衡量它们之前,您仍然需要知道这些情况可能是什么。 @Neil:不,我给你五个理由 - 1) 我不擅长基准测试。我会进行 500 万次迭代,但我不相信结果。 2)我不知道是否存在狭窄的情况,即使一个简单的 bm 显示相同的时钟,它也可能有所不同。 3)我需要比编译器更快的答案。 4) 改进 SO 数据库的问题/答案范围。 5)为了获得一千个新的代表点......老实说,我看到你在各处投票和近距离投票。请放下它。 @Neil:即使 RA 可以编写一个循环来进行 500 万次迭代,他也很可能不会遇到它确实产生影响的有趣案例,因为他不知道他应该看看为他们。如有必要,众包 SO 的答案可以提供有关如何测试的想法。 【参考方案1】:while()
是否比 for()
快/慢?让我们回顾一下关于优化的一些事情:
编译器编写者非常努力地通过减少对跳转、比较、递增和它们生成的其他类型指令的调用来缩短周期。
另一方面,调用指令会消耗更多的周期,但编译器几乎无能为力来消除这些周期。
作为程序员,我们编写了很多函数调用,有些是因为我们的本意,有些是因为我们很懒,有些是因为编译器在不明显的情况下将它们插入。
大多数时候,这并不重要,因为硬件如此之快,而我们的工作如此之少,以至于计算机就像一只狼吞虎咽地乞求更多食物的小猎犬。
但是,有时工作量大到足以导致性能成为问题。
那我们该怎么办?更大的回报在哪里?
让编译器减少循环等几个周期? 查找不需要-真的- 需要做这么多的函数调用?编译器不能做后者。只有我们程序员可以。
我们需要学习或被教导如何做到这一点。它不是自然而然的。 我们天生就倾向于做出错误的猜测,然后在他们身上下注。 获得更好的算法是一个开始,但只是一个开始。如果确实他们知道怎么做,我们的老师需要教这个。
分析器是一个开始。 I do this.
当被问及你为什么抢劫银行?时,Willie Sutton 的杜撰引述:因为钱就在那儿。
如果你想节省周期,找出它们在哪里。
【讨论】:
【参考方案2】:我编写编译器。我们将所有“结构化”控制流(if
、while
、for
、switch
、do
...while
)编译成条件和无条件分支。然后我们分析控制流图。由于 C 编译器无论如何都必须处理一般的 goto
,因此最简单的方法是将 everything 简化为分支和条件分支指令,然后一定要妥善处理这种情况。 (C 编译器不仅要处理好手写代码,还要处理自动生成的代码,这些代码可能有很多很多 goto
语句。)
【讨论】:
+ 我还编写了编译器和其他代码生成器。对于编写编译器的人来说,低级优化是一个很好的主题。对于使用编译器的人来说,影响代码速度的方法要大得多,主要是避免过度设计。 是的,但我确实对分析有点不满。只有其中一些实际上对整个调用堆栈进行了采样,我认为它们中的任何一个都无法说明包含任何给定调用站点的样本百分比,这正是调用站点的成本。他们对函数比函数调用更感兴趣,所以你必须在函数中翻找需要修复的地方。 ... 一些较新的在语句级别提供统计信息,但他们仍然坚持计算执行和/或计时持续时间的概念。您必须将它们相乘,然后除以某个总时间,在感兴趣的区间内,开始接近重要的事情。 ...他们还坚持这样的想法:1)统计数据必须具有高精度,2)上帝禁止我们放慢程序。 @Mike:我还在等待一个非常好的分析器。像 gprof 这样的 PC 采样是可以的,但是在递归和虚拟方法调用方面表现不佳。成本中心分析和 valgrind 有一些令人喜欢的地方,但总的来说,我希望看到更好的工具和更好的最低标准。【参考方案3】:在某些情况下会有所不同。
如果您正处于这种差异很重要的地步,您要么需要选择更好的算法,要么开始使用汇编语言进行编码。相信我,汇编代码比修复编译器版本更可取。
【讨论】:
【参考方案4】:补充一个答案:根据我的经验,优化软件就像是剃掉男人的浓密大胡子。
首先,您用剪刀将其切成大块(将整个四肢从呼叫树上剪掉)。 然后你用电动剪短(调整算法)。 最后你用剃须刀把它刮掉,去掉最后一点点(低级优化)。最后一点是for()
和while()
之间的区别可能(但可能不会)产生影响。
附:我认识的程序员(他们都非常优秀,我怀疑是一个有代表性的样本)基本上是从另一个方向开始的。
【讨论】:
我会赞成你对创意类比的回答(顺便说一句,这是正确的),但这不是 BeardOverflow :)【参考方案5】:continue 在 for 和 while 中的行为不同:在 for 中,它改变计数器,在 虽然,但通常不会
【讨论】:
你说的是哪个柜台? 在下面的代码中,i 是一个计数器: for ( i = 1; i while() 循环中没有计数器。只有循环控制表达式。【参考方案6】:简短回答:不,它们完全相同。
猜测理论上它可能取决于编译器;一个真正坏掉的人可能会做一些稍微不同的事情,但我会感到惊讶。
只是为了好玩,这里有两个变体,它们使用 Ubuntu 附带的 x86 gcc 版本 4.3.3 编译成对我来说完全相同的汇编代码。您可以在 linux 上使用 objdump 检查最终二进制文件中生成的程序集。
主函数() #如果 1 诠释 i = 10; 做 printf("%d\n", i); 当我); #别的 诠释 i = 10; for (; i; --i) printf("%d\n", i); #万一编辑:这是一个“带橙子的橙子”while 循环示例,它也可以编译成相同的内容:
while(i) printf("%d\n", i); - 一世;【讨论】:
+1 用于进行实验,而不仅仅是“表达意见” 通过实验进行尽职调查的另一个 +1。很好的答案! -1 用于比较苹果(for)和橙子(do-while),这通常是不等价的。您应该改用功能等效的 while 循环。 +1 表示它确实取决于编译器。它也可以与我的 eco32 gcc 后端编译成完全相同的代码(RISC,非常简单的架构)(gcc 4.4) 应该是“while”,而不是“do-while”。只有 #else 案例在第一个 printf 之前测试“i”。【参考方案7】:如果你的 for 和 while 循环做同样的事情,编译器生成的机器代码应该(几乎)相同。
例如在我几年前做的一些测试中,
for (int i = 0; i < 10; i++)
...
和
int i = 0;
do
...
i++;
while (i < 10);
将生成完全相同的代码,或者(Neil 在 cmets 中指出)带有一个额外的 jmp,这不会产生足够大的性能差异来担心。
【讨论】:
“应该”:您有任何确凿的证据来支持您的主张吗? 实际上,在我的 VC++ 6.0 编译器上,它们不会生成完全相同的代码。 for() 版本生成额外的 jmp 指令。 @Neil:将您的实验作为可以复制的答案发布怎么样。 在人们开始兴奋之前,我应该指出 jmp 只取一次,无论循环多少次迭代。我的观点是代码不完全相同,应该根据经验检查这些内容。 你为什么把while改成do-while?在某些情况下它可能会快得多,但在其他情况下它会燃烧你。【参考方案8】:没有语义差异,也不需要任何编译差异。但这取决于编译器。所以我尝试使用 g++ 4.3.2、CC 5.5 和 xlc6。
g++、CC 相同,xlc 不同
xlc 的不同之处在于初始循环条目。
extern int doit( int );
void loop1( )
for ( int ii = 0; ii < 10; ii++ )
doit( ii );
void loop2()
int ii = 0;
while ( ii < 10 )
doit( ii );
ii++;
XLC 输出
.loop2: # 0x00000000 (H.10.NO_SYMBOL)
mfspr r0,LR
stu SP,-80(SP)
st r0,88(SP)
cal r3,0(r0)
st r3,64(SP)
l r3,64(SP) ### DIFFERENCE ###
cmpi 0,r3,10
bc BO_IF_NOT,CR0_LT,__L40
...
enter code here
.loop1: # 0x0000006c (H.10.NO_SYMBOL+0x6c)
mfspr r0,LR
stu SP,-80(SP)
st r0,88(SP)
cal r3,0(r0)
cmpi 0,r3,10 ### DIFFERENCE ###
st r3,64(SP)
bc BO_IF_NOT,CR0_LT,__La8
...
【讨论】:
【参考方案9】:在 Atmel ATMega 上,while() 比 for() 快。为什么在 AVR035: Efficient C Coding for AVR 中有解释。
附:未提及原始平台。
【讨论】:
【参考方案10】:使用循环展开进行优化的编译器可能只会在 for 循环的情况下这样做。
【讨论】:
好点!我认为很多人都忘记了的好见解。优化是一个不容忽视的方面。【参考方案11】:理想情况下应该是相同的,但最终取决于您的编译器/解释器。可以肯定的是,您必须测量或检查生成的汇编代码。
证明可能存在差异:这些行使用 cc65 生成不同的汇编代码。
for (; i < 1000; ++i);
while (i < 1000) ++i;
【讨论】:
【参考方案12】:可能只有编码风格。
如果您知道迭代次数。 如果您不知道迭代次数。【讨论】:
并非所有 for() 循环都有固定、已知或可知的迭代次数。 “测试”表达式可以是任何东西,包括函数调用。同样,“下一个”表达式也可以是任何东西。【参考方案13】:while
循环测试中的变量范围比for
循环头中声明的变量范围更宽。
因此,如果将变量保持更长时间作为副作用存在性能影响,那么在 while 和 for 循环之间进行选择(而不是将 while 包装在 中以减少其变量的范围)。
一个例子可能是一个并发集合,它计算引用它的迭代器的数量,如果存在多个迭代器,它会应用锁定来防止并发修改,但是如果只有一个迭代器引用它,作为优化会忽略锁定.如果您在同一容器上使用不同命名的迭代器的函数中有两个 for
循环,则将采用快速路径,但使用两个 while
循环将采用慢速路径。同样,如果对象很大(更多缓存流量)或使用系统资源,则可能会影响性能。但我想不出一个真实的例子,我见过它会在哪里产生影响。
【讨论】:
我喜欢“将 while 包裹在 中以缩小其变量的范围。” 但这假设目的是让变量有更长的寿命。 for() 的伟大之处在于您可以选择声明一个变量,其范围是循环的生命周期。 while() 没有给你那个选项。【参考方案14】:两者是等价的。这是语义问题。
唯一的区别可能在于 do...while 构造,您将条件的评估推迟到 主体之后,因此可以节省 1 次评估。
i = 1; do ... i--; while( i > 0 );
相对于
for( i = 1; i > 0; --i )
....
【讨论】:
【参考方案15】:就性能而言,它们是相同的。我倾向于在等待状态更改时使用while
(例如等待填充缓冲区),在处理大量离散对象(例如遍历集合中的每个项目)时使用for
。
【讨论】:
【参考方案16】:没有。如果他们在做相同的事情,他们会编译成相同的代码——正如你所说,这是关于语义的。选择最能代表您想要表达的内容。
【讨论】:
以上是关于for() 和 while() 之间有啥性能差异吗?的主要内容,如果未能解决你的问题,请参考以下文章
哈希和升序索引之间的 Mongodb 性能差异(有啥理由不在无序字段中使用哈希?)
for 循环和 for-each 循环之间是不是存在性能差异?