重新排列条件评估会加快循环吗?

时间:2009-04-09 13:13:59

标签: c++ optimization micro-optimization premature-optimization

有点奇怪:前一段时间我被一位朋友告知我重新安排了这个例子for循环:

for(int i = 0; i < constant; ++i) {
    // code...
}

为:

for(int i = 0; constant > i; ++i) {
    // code...
}

会稍微提高C ++的性能。我没有看到如何将常量值与变量进行比较比反之亦然,并且我运行的一些基本测试没有显示两种实现之间的速度差异。测试这个Python while循环也是如此:

while i < constant:
    # code...
    i += 1

VS

while constant > i:
    # code...
    i += 1

我错了吗?我的简单测试不足以确定速度变化吗?这是否适用于其他语言?或者这只是一种新的最佳实践?

13 个答案:

答案 0 :(得分:45)

更多的是在C ++民间传说中,手工微优化在特定版本的特定编译器上运行一次,并且随着某种传说将拥有者与普通群体区分开来传递下去。这是垃圾。剖析是真理。

答案 1 :(得分:17)

可能不是,但如果确实如此,编译器可能会自动为您进行优化。所以无论如何都要使你的代码最具可读性。

答案 2 :(得分:10)

我的怀疑是你的朋友100%错了。但我不相信我的意见,而不是相信你的朋友。事实上,如果存在性能问题,那么只有一个人应该信任。

Profiler

这是唯一的方式,您可以声称任何一种方式比另一种更快或更快的权威。

答案 3 :(得分:8)

你给出的例子在C ++中绝对没有性能差异,我怀疑他们在Python中也会有所不同。

也许你会将它与其他优化混淆:

for (int i = 0; i < variable; ++i)

// ...vs...

for (int i = variable; i ; --i)

后者在某些体系结构中更快,因为递减变量的行为将设置零标志,然后可以在跳转 - 如果不是零指令中检查,给出循环迭代和条件一次性。前一个示例需要执行显式比较或减法来设置标志,然后根据它进行跳转。

然而,大​​多数情况下编译器可以将第一种情况优化为第二种情况(特别是如果它看到该变量实际上是一个常量),并且在一些编译器/体系结构组合指令上可能是生成,使第一个方法更像第二个方法。这样的事情只值得尝试,如果你有一个严密的内循环,你的探查器告诉你是昂贵的,但你永远不会注意到差异,否则,如果有一个。

答案 4 :(得分:5)

假设short-circuit evaluation,唯一一次这会产生很大影响的是你在循环中调用慢速函数。例如,如果您有一个从数据库查询值并返回它的函数,那么这个:

while(bContinue && QueryStatusFromDatabase==1){
}  //while

会比以下快得多:

while(QueryStatusFromDatabase==1 && bContinue){
}  //while

即使它们在逻辑上相同。

这是因为第一个可以在一个简单的布尔值为FALSE时立即停止 - 只有在布尔值为TRUE时才运行查询,但第二个将总是运行查询。

除非您需要将所有可能的CPU周期从循环中挤出,否则这些极端情况可能是唯一值得花时间的情况。可以这样想:为了弥补你花时间问这个问题可能需要几十亿次循环。

最糟糕的是当你有一个函数作为一个条件,并且该函数具有代码中某些其他位置秘密期望的副作用。因此,当您进行小优化时,副作用只会发生某些时间,并且您的代码会以奇怪的方式中断。但这有点像切线。对你的问题的简短回答是“有时,但通常没关系。”

答案 5 :(得分:4)

虽然分析是最好的,但它不是唯一方式。

您可以比较每个选项创建的程序集,这对于像这样的微优化不应该是不可能的。对硬件平台命令进行一些研究可以让你有一个好主意,如果这个变化完全不同,以及它可能有不同的表现。我假设您将计算移动次数并比较命令。

如果您的调试器允许您在源代码和反汇编视图之间切换,那么踩到它应该非常简单。

答案 6 :(得分:3)

更好的做法是不要偏离这样的优化调整,这会给你带来微不足道的好处(假设 是一个调整)。

答案 7 :(得分:2)

任何理智的编译器都将以相同的方式实现。如果某个体系结构上的一个比另一个体系结构更快,编译器将以这种方式对其进行优化。

答案 8 :(得分:1)

与0比较非常快,所以这实际上会稍快一点:

for (int i = constant; i > 0; --i)
{ 
  //yo
}

我认为最好在任何情况下都使用!=,因为它更易于检测一个错误,并且是使用具有非连续数据结构的迭代器的唯一方法,例如链表。

答案 9 :(得分:0)

今天,在一个好的编译器上,根本没有。

首先,操作数顺序对我看到的指令集没有任何影响。其次,如果有一个,任何体面的优化器都会选择更好的优化器。

但是,我们不应盲目地放弃表现。响应性仍然很重要,计算时间也是如此。特别是在编写库代码时,您不知道何时连续调用200万次。

此外,并非所有平台都是平等的。嵌入式平台通常在低(呃)处理能力和实时处理要求的基础上受到低于标准的优化器的影响。

在桌面/服务器平台上,权重已经转移到封装良好的复杂性,从而实现更好的缩放算法。

微观优化只有当他们伤害其他内容时才会变坏,例如可读性,复杂性或可维护性。当其他条件相同时,为什么不选择更快的?


有一段时间在x86上结束循环为零(例如通过倒计时)实际上可以给紧密循环带来显着的改进,因为DEC CX/JCXNZ更快(它仍然可能,因为它可以节省比较的寄存器/存储器访问;编译器执行优化通常超出现在的范围)。你朋友听到的内容可能是错误的版本。

答案 10 :(得分:0)

我谦卑地建议,在某些架构上的某些编译器上,以下内容可以比变体更有效地降低:

i = constant - 1
while (--i) {
}

获取常量迭代。

正如许多评论所暗示的那样,编译器会很好地为您优化循环(编译器优化人们花了很多时间考虑它)。合法的代码可能更有价值,但是YMMV!

如果您真的希望优化超出您认为的编译器可能做的,我建议查看高级语言生成的程序集,并考虑从那里进一步优化。

在高级别上,您还可以通过使用OpenMP获得更高的性能,或者通过向量指令集(例如MMX)在较低级别上获得显着更高的性能,以在单个指令中进行多次计算。这有点超出了问题的范围,你必须提供更多关于循环在做什么的有用建议的信息。

希望有帮助&amp;欢呼声。

答案 11 :(得分:0)

提供的优化只会为给定的编译器(可能)优化更多。抽象地说,它应该生成相同的代码。

如果您正在进行微优化 - 假设满足微优化的要求 - 您的第一步应该是查看生成的装配,然后查看您的架构的装配手册。

例如,i ++可能比i + 1更快。要看。在天真的CPU中,等于0比低于等于快得多。如果您的编译器/ CPU不支持指令重新排序,您可能会发现通过计算进行散布分配会加快代码的速度。 (某些计算可能会导致管道停顿)但是,您必须明确确定您的编译器/体系结构组合。

坦率地说,我不打算做这个级别的优化,除非我绝对需要来自我的处理器的每一个周期。传统上,图形或科学计算是你需要这种东西的地方[*]。

*我知道一个程序,经过几个月的优化在现代机器上,仍然需要几个月的时间来处理数据。单个数据集的运行时间在周范围内。有很多数据可供使用....

答案 12 :(得分:-1)

这绝对是微优化的一个例子,真的不需要做。

确实(特别是)在C ++中,后增量操作和预增量操作之间的性能差异很小,但今天的编译器中的差异通常可以忽略不计。更改条件顺序的原因是由于从后增量到预增量的变化。