迭代一次和迭代两次之间的性能差异?

时间:2010-10-24 02:50:33

标签: performance loops iteration

考虑类似......

for (int i = 0; i < test.size(); ++i) {
        test[i].foo();
        test[i].bar();
}

现在考虑..

for (int i = 0; i < test.size(); ++i) {
        test[i].foo();
}
for (int i = 0; i < test.size(); ++i) {
        test[i].bar();
}

这两者之间的时间差异很大吗?即实际迭代的成本是多少?看起来你重复的唯一真实操作是增量和比较(尽管我认为这对于非常大的n来说会变得很重要)。我错过了什么吗?

5 个答案:

答案 0 :(得分:2)

首先,如上所述,如果您的编译器无法优化size()方法,因此它只调用一次,或者只是一次读取(没有函数调用开销),那么它会受到伤害。

但是你可能想要关注第二个效果。如果您的容器尺寸足够大,那么第一种情况将会更快。这是因为,当它到达test[i].bar()时,test[i]将被缓存。第二种情况是使用分割循环,会破坏缓存,因为test[i]总是需要从每个函数的主内存重新加载。

更糟糕的是,如果你的容器(std::vector,我猜)有这么多项目,它不会全部适合内存,而且其中一些必须在你的磁盘上交换,那么区别将是巨大的,因为您必须从磁盘两次加载

但是,最后还有一件事需要考虑:如果函数调用之间没有顺序依赖关系(实际上,容器中的不同对象之间),这一切只会产生影响。因为,如果你解决了,第一种情况就是:

test[0].foo();
test[0].bar();
test[1].foo();
test[1].bar();
test[2].foo();
test[2].bar();
// ...
test[test.size()-1].foo();
test[test.size()-1].bar();

而第二个确实:

test[0].foo();
test[1].foo();
test[2].foo();
// ...
test[test.size()-1].foo();
test[0].bar();
test[1].bar();
test[2].bar();
// ...
test[test.size()-1].bar();

因此,如果您的bar()假设所有foo()都已运行,那么如果您将第二种情况更改为第一种情况,则会将其中断。同样,如果bar()假定foo()尚未在后来的对象上运行,那么从第二种情况转移到第一种情况将会破坏您的代码。

所以要小心并记录下你的工作。

答案 1 :(得分:0)

对于您的示例,您还需要考虑.size()的费用,因为在大多数语言中每次i增量都需要进行比较。

它有多贵?那取决于它,它肯定都是相对的。如果.foo().bar()很昂贵,那么实际迭代的成本可能相比之下微不足道。如果它们非常轻巧,那么它的执行时间就会占很大比例。如果您想了解特定案例测试,这是确定您的具体方案的唯一方法。

就个人而言,我确实会在单一迭代中使用廉价的一面(除非您需要在.foo()来电之前进行.bar()次调用。)

答案 2 :(得分:0)

我认为.size()将是不变的。否则,第一个代码示例可能与第二个代码示例不同。

大多数编译器可能会在循环开始之前将.size()存储在变量中,因此.size()时间将减少。

因此,两个for循环中的东西的时间是相同的,但另一个部分将是两倍。

答案 3 :(得分:0)

这种比较有很多方面。

首先,两个选项的complexity都是O(n),所以差异不是很大。我的意思是,如果你用大n和“重”操作.foo()bar()编写相当大而复杂的程序,你就不必关心它。因此,只有在非常小的简单程序(例如,这是用于嵌入式设备的程序)的情况下,您必须关心它。

其次,它将取决于编程语言和编译器。我确信,例如,大多数C ++编译器将优化您的第二个选项以生成与第一个相同的代码。

第三,如果编译器没有优化您的代码,性能差异将在很大程度上取决于目标处理器。在汇编命令的术语中考虑循环 - 它看起来像这样(伪汇编语言):

LABEL L1:
          do this    ;; some commands
          call that
          IF condition
          goto L1
          ;; some more instructions, ELSE part

即。每个循环段落只是IF语句。但现代处理器不喜欢IF。这是因为处理器可能会重新排列指令以预先执行它们或仅仅是为了避免空闲。使用IF(实际上是条件转到或跳转)指令,处理器不知道它们是否可以重新排列操作。
还有一种称为分支预测器的机制。 From material of Wikipedia

branch predictor is a digital circuit that tries to guess which way a branch (e.g. an if-then-else structure) will go before this is known for sure.

IF的“软化”效果,如果预测器的猜测错误,则不会执行优化。

所以,你可以看到你的选择有很多条件:目标语言和编译器,目标机器,它的处理器和分支预测器。这一切都使得系统非常复杂,你无法预见到什么样的结果。我相信,如果你不处理嵌入式系统或类似的东西,最好的解决方案就是使用你更舒服的形式。

答案 4 :(得分:0)

性能标签,对。

只要你专注于这个或那个次要代码段的“成本”,你就会忘记更大的图景(隔离);而你的意图是证明某种东西,在更高层次(在你孤立的背景之外),只是不好的做法,并违反准则。这个问题太低了,因此太孤立了。一组集成组件的系统或程序将比隔离组件的集合执行得更好。

当循环本身不必要地重复时,这个或那个孤立的组件(在循环内工作)快速或快速的事实是无关紧要的,因此需要两倍的时间。

鉴于你有一辆家用车(CPU),为什么你会在地球上:

  • 坐在家里送你的妻子去购物
  • 等到她回来
  • 坐车,外出购物
  • 让她等到你回来 如果需要说明,你将花费(a)几乎一半的辛苦资源执行一次旅行并同时购物;(b)有这些资源可以在你回家时一起玩。

它与星期六9点的汽油价格,咖啡馆咖啡的磨损时间或每次迭代的成本无关。

是的,时间和使用的资源存在很大差异。但成本不仅仅是每次迭代的开销;这是一次有组织的旅行与两次连续旅行的总成本。

表现与建筑有关;从来没有做过两次(你可以做一次),这是更高层次的组织;整合构成整体的部分。它不是关于计算bowser的便士或每次迭代的周期;那些是较低的组织秩序;这是一个零散的部分(不是一个系统的整体)的集合。

Masseratis无法以快车速度穿越交通拥堵。