【问题标题】:Performance difference between iterating once and iterating twice?迭代一次和迭代两次之间的性能差异?
【发布时间】:2011-04-29 17:59:10
【问题描述】:

考虑类似...

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();
}

这两者之间花费的时间有很大差异吗? IE。实际迭代的成本是多少?似乎您要重复的唯一实际操作是增量和比较(尽管我认为这对于非常大的 n 会变得很重要)。我错过了什么吗?

【问题讨论】:

    标签: performance loops iteration


    【解决方案1】:

    对于您的示例,您还担心.size() 的成本有多高,因为在大多数语言中,每次i 递增时都会对其进行比较。

    它有多贵?好吧,这取决于,这当然都是相对的。如果.foo() 和.bar() 很昂贵,那么实际迭代的成本相比之下可能微不足道。如果它们非常轻量级,那么它将占您执行时间的更大百分比。如果您想了解特定案例测试它,这是确定您的特定场景的唯一方法。

    就个人而言,我肯定会选择单次迭代以降低成本(除非您需要在 .bar() 调用之前进行 .foo() 调用)。

    【讨论】:

    • 假设 size() 已优化。但我只是想确保唯一的额外成本是增量和比较。
    • 还有一个问题是如何在循环中优化 test[i]。一次迭代可能只需要一次取消引用。但是,与往常一样,测试是回答性能问题的最佳方式。
    • @Brian - 同意,并在一个循环中对其自身/整体进行至少 3 次测试(用于阀芯上/下变化),取中间结果。这取决于语言的必要性。
    【解决方案2】:

    我假设.size() 将保持不变。否则,第一个代码示例可能与第二个代码示例不同。

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

    因此两个for循环里面的东西的时间会是一样的,但是其他部分会是原来的两倍。

    【讨论】:

      【解决方案3】:

      这样的比较有很多方面。

      首先,两个选项的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 的这种“软化”效果,如果预测器的猜测错误,则不会执行优化。

      因此,您可以看到两个选项都有大量条件:目标语言和编译器、目标机器、处理器和分支预测器。这一切都使系统变得非常复杂,您无法预见会得到什么确切的结果。我相信,如果您不处理嵌入式系统或类似的东西,最好的解决方案就是使用您更熟悉的形式。

      【讨论】:

      • IIRC,分支预测通常适用于不太可能有breaks 的循环。
      【解决方案4】:

      首先,如上所述,如果您的编译器无法优化 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() 没有在以后的对象上运行,那么从第二种情况转移到第一种情况会破坏您的代码。

      所以要小心并记录你所做的事情。

      【讨论】:

        【解决方案5】:

        性能标签,对。

        只要您专注于这个或那个次要代码段的“成本”,您就会忘记更大的图景(孤立);并且您的意图是在更高的级别(在您的孤立环境之外)证明某些事情是错误的做法,并且违反了指导方针。这个问题太低级了,因此太孤立了。由一组集成组件组成的系统或程序将比一组独立组件执行得更好。

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

        鉴于您拥有一辆家用汽车 (CPU),您到底为什么要这样做:

        • 坐在家里送老婆出去购物
        • 等她回来
        • 坐车出去购物
        • 让她等你回来 如果需要说明,您将 (a) 将几乎一半来之不易的资源用于一次旅行和购物,并且 (b) 有这些资源可以在您回家后一起玩乐。

        这与周六 9:00 的汽油价格、在咖啡馆磨咖啡所需的时间或每次迭代的成本无关。

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

        性能与架构有关;永远不要做任何事情两次(你可以做一次),这是更高层次的组织;将构成整体的部分整合在一起。这不是要在 Bowser 上计算便士或每次迭代的周期;这些是较低的组织顺序;它调整了零散部分的集合(而不是系统的整体)。

        玛莎拉蒂无法比旅行车更快地通过交通拥堵。

        【讨论】:

        • 你似乎对你寻求的答案有固定的想法,如此固定,以至于你无法摆脱它足够长的时间来进行类比。您是否听说过答案可能不是您所寻求的,超出您所寻求的答案?您是否愿意了解您的问题空间或只是寻求狭隘地确认一个孤立的想法?为了注意到这一点,如果你是认真的,你需要放弃你对答案的固定想法,真诚地寻求。固定的头脑不应该超过第一段。
        • 单独(即与我已经提出的一点不同,这与您的问题有关),请参阅 Donald Knuth “”程序员浪费大量时间思考或担心,他们程序中非关键部分的速度……过早的优化是万恶之源。 “”。
        猜你喜欢
        • 2011-03-12
        • 2021-10-05
        • 2019-06-06
        • 2017-09-21
        • 2023-04-02
        • 2017-07-05
        • 2011-10-22
        • 2014-09-06
        相关资源
        最近更新 更多