【问题标题】:Java ArrayList for-loop optimizationJava ArrayList for 循环优化
【发布时间】:2016-11-16 19:54:51
【问题描述】:

在 Dov Bulka 的 Java Performance and Scalability: Volume 1 中,作者提到使用诸如

之类的东西循环 ArrayList
for (int i = 0; i < vector.size(); i++) {
    // do something that does not modify vector size...
}

实际上是一个小的优化问题,因为vector.size()的不断计算,因此暗示诸如

之类的东西
int size = vector.size();    
for (int i = 0; i < size; i++) {
    // do something that does not modify vector size...
}

实际上效率更高一些。由于本书写于 2000 年,作者使用的是 Sun 1.2.2 JDK。

这种情况是否仍然适用于较新的 JDK?或者,Java 编译现在是否足够聪明,可以消除这些低效率,尽管它们可能很小?

编辑:我不担心代码中的这些微小优化;我只是对 JDK 的演变感到好奇。

【问题讨论】:

  • 除非你能证明重复调用size()是你代码中的瓶颈,否则不要浪费你的时间去担心这样的微优化。在任何情况下,这种优化只在特定条件下是安全的(即,当您在循环中时,没有其他线程可以更改 vector 的大小)。
  • 考虑使用for-each 循环而不用担心List&lt;&gt; 的内部结构。在 java 8 中,List#forEach(...) 会隐藏得更多。
  • 但是,“除非绝对必须在循环中执行,否则不要在循环中执行某些操作”的基本规则仍然适用。无论技术如何发展,在循环中减少计算量是一件好事。

标签: java for-loop optimization arraylist


【解决方案1】:

检查循环字节码:

12: iload_3
13: aload_2
14: invokeinterface #4,  1            // InterfaceMethod java/util/List.size:()I
19: if_icmpge     31
22: iinc          1, 1
25: iinc          3, 1
28: goto          12

将其放入可变字节码中:

10: aload_2
11: invokeinterface #4,  1            // InterfaceMethod java/util/List.size:()I
16: istore_3
17: iconst_0
18: istore        4
20: iload         4
22: iload_3
23: if_icmpge     35
26: iinc          1, 1
29: iinc          4, 1
32: goto          20

似乎它每次都调用它,所以实际上将它放入变量中更快,我不会担心它。请注意,我是字节码的新手,我可能完全错了。

【讨论】:

【解决方案2】:

这种情况是否仍然适用于较新的 JDK?或者,Java 编译现在是否足够聪明,可以消除这些低效率,尽管它们可能很小?

考虑到“Java 编译器”javac,什么都没有改变,而且很可能永远不会改变。 任何优化不是它的工作。 所以查看生成的字节码是没有意义的。

JIT 编译器 (Oracle Hotspot) 在运行时完成优化。它当然可以内联这样一个简单的方法,并且很可能还可以将大小缓存在寄存器中,从而消除内存访问。为此,它需要能够将所有内容内联到方法中 - 否则无法保证 vector.size 不会改变。

PS:真正的性能问题可能是Vector 的使用,多年来这是一个毫无意义的课程。首选ArraysList

【讨论】:

    【解决方案3】:

    这是size() int ArrayList类的实现

    /**
     * Returns the number of elements in this list.
     *
     * @return the number of elements in this list
     */
    public int size() {
        return size;
    }
    

    在这种情况下,由于它存储在对象的属性中,所以它只是一个函数调用并返回大小的值(不计算它)。所以这里只是为了防止一个函数调用。

    将大小存储在变量中绝对是有意义的,如果 size() 方法在每次调用时遍历列表以计算大小。

    【讨论】:

    • 没有。只要这很重要,方法调用就会被内联。可能有些情况不会发生,我期待看到一个真实的案例。
    【解决方案4】:

    因为 size() 是一种方法,如果在循环中每次都对其进行评估,它会比评估一次并将其存储在变量中要慢。问题不在于它再次计算数组大小;相反,它根本就是调用函数的开销。无论方法中有什么,这都会损害性能(当然,一个漫长、缓慢、复杂的函数比简单的 getter 对它的损害更大)。

    我很小心地说“如果”每次都会评估它,因为编译器可能会决定内联函数调用,这将消除开销并且循环将同样快。这与 for-each 与通用 for 循环的辩论相同。如果 for-each 函数调用没有内联,它会比没有函数调用的一般 for 循环慢。

    在某些情况下,这肯定会对性能产生很大影响,因此最好注意这些细微之处。需要高吞吐量的实时信号处理算法是可能对不必要的开销敏感的程序的好例子。当然,这些通常不是用 java 编写的,但仍然是,但是了解这些东西是件好事。

    【讨论】:

      猜你喜欢
      • 2016-02-23
      • 2011-08-30
      • 1970-01-01
      • 2015-04-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-07
      • 2018-12-23
      相关资源
      最近更新 更多