我已經運行您提供的代碼,也很驚訝地看到這些性能差異。好奇心帶領我開始調查並發現,儘管這些循環似乎在做同樣的事情,但它們之間還是有一些重要的區別。
我的這些循環的第一次運行後結果爲:
for loop: 1.43100
do-while: 0.51300
while: 1.54500
但是當我運行這三個迴路至少10次,然後每個循環的性能幾乎相同。
for loop: 0.43200
do-while: 0.46100
while: 0.42900
的JIT能夠隨着時間的推移,以優化這些循環,但必須有一些差異性導致這些環路有不同的初始性能。事實上實際上有兩個差異:
爲了簡單起見做較少的比較假定L = 1
long s1 = 0;
for (int i=0; i < L; i++) {
for (int j = 0; j < L; j++) {
s1 += 1;
外環:0 內循環:0 內環:1 外環:在總
int i = 0;
long s2 = 0;
do {
i++;
int j = 0;
do {
s2 += 1;
j++;
} while (j < L);
} while (i < L);
內環1個
4比較:1 外環:1
共計2次比較
爲了進一步調查的目的,我已經略有改變你的類,不影響它的工作方式。
public class Loops {
final static int L = 100000; // number of iterations per loop
public static void main(String[] args) {
int round = 10;
while (round-- > 0) {
forLoop();
doWhileLoop();
whileLoop();
}
}
private static long whileLoop() {
int i = 0;
long s3 = 0;
while (i++ < L) {
int j = 0;
while (j++ < L) {
s3 += 1;
}
}
return s3;
}
private static long doWhileLoop() {
int i = 0;
long s2 = 0;
do {
int j = 0;
do {
s2 += 1;
} while (++j < L);
} while (++i < L);
return s2;
}
private static long forLoop() {
long s1 = 0;
for (int i = 0; i < L; i++) {
for (int j = 0; j < L; j++) {
s1 += 1;
}
}
return s1;
}
}
然後編譯它,並調用javap -c -s -private -l Loop
得到字節碼。
首先是doWhileLoop的字節碼。
0: iconst_0 // push the int value 0 onto the stack
1: istore_1 // store int value into variable 1 (i)
2: lconst_0 // push the long 0 onto the stack
3: lstore_2 // store a long value in a local variable 2 (s2)
4: iconst_0 // push the int value 0 onto the stack
5: istore 4 // store int value into variable 4 (j)
7: lload_2 // load a long value from a local variable 2 (i)
8: lconst_1 // push the long 1 onto the stack
9: ladd // add two longs
10: lstore_2 // store a long value in a local variable 2 (i)
11: iinc 4, 1 // increment local variable 4 (j) by signed byte 1
14: iload 4 // load an int value from a local variable 4 (j)
16: iload_0 // load an int value from a local variable 0 (L)
17: if_icmplt 7 // if value1 is less than value2, branch to instruction at 7
20: iinc 1, 1 // increment local variable 1 (i) by signed byte 1
23: iload_1 // load an int value from a local variable 1 (i)
24: iload_0 // load an int value from a local variable 0 (L)
25: if_icmplt 4 // if value1 is less than value2, branch to instruction at 4
28: lload_2 // load a long value from a local variable 2 (s2)
29: lreturn // return a long value
現在while循環的字節碼:
0: iconst_0 // push int value 0 onto the stack
1: istore_1 // store int value into variable 1 (i)
2: lconst_0 // push the long 0 onto the stack
3: lstore_2 // store a long value in a local variable 2 (s3)
4: goto 26
7: iconst_0 // push the int value 0 onto the stack
8: istore 4 // store int value into variable 4 (j)
10: goto 17
13: lload_2 // load a long value from a local variable 2 (s3)
14: lconst_1 // push the long 1 onto the stack
15: ladd // add two longs
16: lstore_2 // store a long value in a local variable 2 (s3)
17: iload 4 // load an int value from a local variable 4 (j)
19: iinc 4, 1 // increment local variable 4 (j) by signed byte 1
22: iload_0 // load an int value from a local variable 0 (L)
23: if_icmplt 13 // if value1 is less than value2, branch to instruction at 13
26: iload_1 // load an int value from a local variable 1 (i)
27: iinc 1, 1 // increment local variable 1 by signed byte 1
30: iload_0 // load an int value from a local variable 0 (L)
31: if_icmplt 7 // if value1 is less than value2, branch to instruction at 7
34: lload_2 // load a long value from a local variable 2 (s3)
35: lreturn // return a long value
爲了使輸出更易讀我已經追加描述每個指令並根據該Java bytecode instruction listings什麼意見。
如果仔細觀察,您會發現這兩個字節碼之間存在重要區別。 while循環(對於for循環也是如此)在字節碼的末尾定義了if語句(if_icmplt
指令)。這意味着要檢查第一個循環的退出條件,必須調用到第26行的跳轉條件,並且類似地,第17條循環跳轉到第17行。
是Mac OS X上使用javac 1.6.0_45生成上述字節碼
摘要
我認爲不同量比較的加的GOTO指令在while和for循環字節碼的存在負責這些循環之間的性能差異。
我無法重現您的號碼。 while循環對於我來說運行速度相同,for循環略慢。 – Mysticial 2013-04-25 03:53:52
在任何情況下,這不是一個特別好的基準,因爲編譯器或JIT可能能夠完全移除內部循環。 – Mysticial 2013-04-25 03:55:07
必須這樣 - 某種優化只能在do-while循環中執行。不過,我想知道更多關於這種機制的信息。 – JohnF 2013-04-25 04:09:52