2016-11-15 70 views
2

在多夫布尔卡的Java性能和可扩展性:第1卷,笔者提到,遍历一个ArrayList与作为Java的ArrayList的for循环优化

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的发展感到好奇。

+9

除非您可以证明重复调用'size()'是代码中的瓶颈,否则不要浪费时间去担心这种微优化。无论如何,这种优化在某些条件下才是安全的(即当你处于循环中时,没有其他线程可以改变'vector'的大小)。 –

+4

考虑使用'for-each'循环,而不用担心'List <>'的内部。在java 8中,'List#forEach(...)'将会更加隐藏它。 – bradimus

+2

但是,“循环中不做某件事,除非你绝对必须在循环中执行”的基本规则仍然适用。无论技术如何发展,在循环中减少计算是件好事。 –

回答

1

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

考虑到“Java编译器”javac,没有任何变化,最可能永远不会。执行任何优化都不是它的工作。 所以看着生成的字节码是毫无意义的。

优化在运行时由JIT编译器(Oracle Hotspot)完成。它肯定可以内联这样一个微不足道的方法,它最有可能还可以将大小缓存在寄存器中,以便消除内存访问。为此,它需要能够将所有内容都嵌入到方法中 - 否则不能保证vector.size不会改变。

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

3

检查在一个循环字节码:

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 

好像它每次调用它,所以实际上把它在一个变量速度更快,我不会为此担心。请注意,我是字节码的新手,我可能完全错误。

+0

该字节码与100%无关,请参阅[我的答案](http://stackoverflow.com/a/40631555/581205)。 – maaartinus

1

这是大小的()实现INT ArrayList类

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

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

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

+0

否。只要这很重要,方法调用就会内联。有些情况下,这种情况不会发生,我期待看到一个真实的案例。 – maaartinus

1

由于size()是一种方法,如果每次在循环中对其进行评估,它将比评估一次并将其存储在一个变量中慢。问题不在于它再次计算阵列大小;相反,它是根本调用函数的开销。无论方法如何,这都会伤害到性能(尽管当然,一个漫长,缓慢,复杂的函数会比一个简单的getter更加伤害它)。

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

确实存在这样的情况,它可以在性能上产生很大的差异,因此很好地了解这些细微之处。需要高吞吐量的实时信号处理算法是可能对不必要开销敏感的程序的很好例子。当然,这些通常不是用java编写的,但仍然是这样,但很好了解这些东西。