【问题标题】:Enhanced for loop performance增强的 for 循环性能
【发布时间】:2012-08-22 18:25:42
【问题描述】:

为此,我和朋友发生了争执。 考虑下面的sn-p,

for(i=0; i<someList.size(); i++) {
    //some logic
    }

这里someList.size()每次迭代都会执行,所以建议将这个大小计算迁移到循环外(之前)。

现在,当我使用这样的扩展 for 循环时会发生什么,

for(SpecialBean bean: someBean.getSpecialList()) {
//some logic
}

是否有必要将someBean.getSpecialList() 移到循环外? 如果我要保留第二个 sn-p,someBean.getSpecialList() 将执行多少次?

【问题讨论】:

    标签: java performance for-loop


    【解决方案1】:

    重复调用list.size() 不会导致任何性能损失。 JIT 编译器很可能会内联它,即使它没有内联,它仍然会非常便宜,因为它只涉及读取字段的值。

    你的第一个例子的一个更严重的问题是循环体必须涉及list.get(i),对于LinkedList,访问第 i 个元素需要 O(i ) 由于指针追逐,成本具有相当大的常数因子,这会转化为 CPU 级别上的数据相关负载。 CPU 的预取器无法优化这种访问模式。

    这意味着当应用于 LinkedList 时,整体计算复杂度将为 O(n2)。

    您的第二个示例通过Iterator 编译为迭代,并且只会评估someBean.getSpecialList().iterator() 一次。 iterator.next() 的成本在所有情况下都是不变的。

    【讨论】:

    • 您能否澄清一下 LinkedList(如果使用)将如何导致 O(n^2)?
    • 假设您将遍历该循环体中的列表元素,这涉及到list.get(i),对于链表来说是 O(i)。您将评估 n 次,导致 O(n^2) 整体复杂度。
    • 这个答案是错误的。 LinkedList.size 调用成本为 O(1),因为它是类成员。每次调用都不会计算。看一下 LinkedList 类的源码。
    • @kerberos84 无论如何,这是答案的一个侧面。 O(n2) 复杂度的根本来源是索引访问。请参阅编辑后的答案。
    • @MarkoTopolnik 不,list.size() 是您对 o(n^2) 的推理,并且包含有关该 api 调用的错误信息。因此,对于此答案的更多读者,现在它不再包含错误信息。
    【解决方案2】:

    来自 Joshua Bloch 在 Effective Java 中的第 46 条:

    1.5 版中引入的 for-each 循环消除了混乱 以及隐藏迭代器或索引的错误机会 完全可变。由此产生的成语同样适用于 集合和数组:

    // 迭代集合和数组的首选习惯用法 (元素 e:元素){ 做某事(e); } 当您看到冒号 (:) 时,将其读作“in”。因此,上面的循环读作“对于元素中的每个元素 e”。笔记 使用 for-each 循环没有性能损失,即使 对于数组。事实上,它可能比 在某些情况下是一个普通的 for 循环,因为它计算限制 数组索引只有一次。虽然你可以手动完成(第 45 项), 程序员并不总是这样做。

    另见is-there-a-performance-difference-between-a-for-loop-and-a-for-each-loop

    【讨论】:

      【解决方案3】:

      第一个 sn-p 的替代方案是:

      for(i=0, l=someList.size(); i<l; i++) {
          //some logic
      }
      

      关于 for..each 循环,对getSpecialList() 的调用只会进行一次(您可以通过在方法中添加一些调试/日志来验证这一点)。

      【讨论】:

        【解决方案4】:

        由于扩展循环使用从 Iterable 中获取的 Iterator,因此多次执行 someBean.getSpecialList() 是不可能或不明智的。将其移出循环不会改变循环的性能,但如果它提高可读性,您可以这样做。

        注意:如果您按索引进行迭代,则随机访问集合可能会更快,例如ArrayList,因为它不创建迭代器,但对于不支持随机访问的索引集合来说速度较慢。

        【讨论】:

          【解决方案5】:

          for each 变化将与以下相同

          for (Iterator i = c.iterator(); i.hasNext(); ) {
          doSomething((Element) i.next()); 
          }
          

          来自第 46 条:Prefer for-each 循环而不是 Effective java 的传统 for 循环

          for-each 循环提供了优于传统循环的引人注目的优势 明确的循环和错误预防,没有性能损失。你 应该尽可能使用它。

          所以我的第一个猜测是错误的,在 for each 循环中使用函数没有惩罚。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-02-23
            • 2011-01-20
            • 1970-01-01
            • 2021-11-05
            • 1970-01-01
            • 1970-01-01
            • 2014-06-18
            相关资源
            最近更新 更多