11

有点奇怪:一位朋友不久前告诉我,重新排列这个示例for循环:

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

到:

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

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

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

与:

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

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

4

13 回答 13

45

它更像是 C++ 的民间传说,手动微优化曾经在特定编译器的特定版本上工作过,然后作为某种传说将拥有者与普通群体区分开来。太垃圾了 剖析是真理。

于 2009-04-09T13:18:29.497 回答
17

可能不会,但如果确实如此,编译器可能会自动为您进行优化。因此,无论以何种方式使您的代码最具可读性,都可以这样做。

于 2009-04-09T13:16:33.183 回答
10

我怀疑你的朋友是 100% 错的。但我不会再相信我的意见,就像我不会相信你的朋友一样。事实上,如果出现性能问题,您应该只信任一个人。

探查器

只有这样,您才能以任何权威声称一种方式比另一种方式快或不快。

于 2009-04-09T13:16:52.293 回答
8

您提供的示例在 C++ 中应该绝对没有性能差异,我怀疑它们在 Python 中也会有所不同。

也许您将它与不同的优化混淆了:

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

// ...vs...

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

后者在某些体系结构中更快,因为递减变量的行为将设置零标志,然后可以在非零跳转指令中对其进行检查,从而一次性为您提供循环迭代和条件。前一个示例需要执行显式比较或减法以设置标志,然后基于该标志进行跳转。

但是,大多数时候编译器可以将第一种情况优化为第二种情况(特别是如果它看到变量实际上是一个常量),并且在某些编译器/架构组合上可能会生成使第一种方法更像第二种方法的指令. 只有当你的分析器告诉你有一个紧密的内部循环很昂贵时,这样的事情才值得尝试,但你永远不会注意到差异,即使有一个。

于 2009-04-09T13:47:19.053 回答
5

假设短路评估,唯一一次应该有很大不同的是,如果您在循环中调用了慢速函数。例如,如果您有一个函数从数据库中查询一个值并返回它,那么:

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

会比:

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

尽管它们在逻辑上是相同的。

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

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

最糟糕的是,当您将函数作为条件时,并且该函数具有代码中其他地方秘密预期的副作用。因此,当您进行少量优化时,副作用只会在某些时候发生,并且您的代码会以奇怪的方式中断。但这有点切线。您的问题的简短回答是“有时,但通常没关系”。

于 2009-04-09T13:28:15.667 回答
4

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

您可以比较每个选项创建的程序集,对于像这样的微优化来说,这不应该是不可能的。对您的硬件平台的命令进行一点研究可以让您了解这种变化是否会产生影响以及它可能会如何以不同的方式执行。我假设您将为您的示例计算移动次数并比较命令。

如果您的调试器允许您在单步执行时在源视图和反汇编视图之间切换,这应该很容易。

于 2009-04-09T13:59:29.150 回答
3

最好不要不顾一切地进行这样的优化调整,这会给你带来微不足道的好处(假设它一个调整)。

于 2009-04-09T13:15:54.807 回答
2

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

于 2009-04-09T18:20:44.160 回答
1

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

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

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

于 2009-08-03T06:15:04.397 回答
0

今天,关于一个好的编译器,一点也不。

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

不过,我们不应该盲目地忽视性能。响应性仍然很重要,计算时间也很重要。尤其是在编写库代码时,您不知道何时会连续被调用 200 万次。

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

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

微优化只有在损害其他方面(例如可读性、复杂性或可维护性)时才是不好的。当其他一切都相同时,为什么不选择更快的呢?


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

于 2009-04-09T13:39:50.337 回答
0

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

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

获得不断的迭代。

正如许多评论所暗示的那样,编译器将为您优化循环做得很好(编译器优化人员已经花了很多时间思考它)。清晰的代码可能更有价值,但是 YMMV!

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

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

希望有帮助和欢呼。

于 2009-04-09T15:17:58.593 回答
0

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

如果您正在进行微优化——假设满足微优化的要求——您的第一步应该是查看生成的程序集,然后查看您的体系结构的程序集手册。

例如,i++ 可能比 i+1 快。依靠。在幼稚的 CPU 中,等于 0 比小于值快得多。如果您的编译器/CPU 不支持指令重新排序,您可能会发现在计算中穿插分配会加快您的代码速度。(某些计算可能会导致管道停顿)但这是您必须为您的编译器/架构组合专门确定的内容。

坦率地说,除非我绝对需要处理器的每个最后一个周期,否则我不会费心进行这种级别的优化。传统上,图形或科学计算是你需要这类东西的地方[*]。

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

于 2009-07-31T20:46:18.820 回答
-1

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

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

于 2009-04-09T13:18:47.407 回答