2016-11-15 97 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編寫的,但仍然是這樣,但很好了解這些東西。