2013-04-25 75 views
10

我比較了Java中嵌套for while和do-while循環的效率,並且遇到了一些我需要幫助理解的奇怪結果。Java循環效率

public class Loops { 
    public static void main(String[] args) { 
     int L = 100000; // number of iterations per loop 
     // for loop 
     double start = System.currentTimeMillis(); 
     long s1 = 0; 
     for (int i=0; i < L; i++) { 
      for (int j = 0; j < L; j++) { 
       s1 += 1; 
      } 
     } 
     double end = System.currentTimeMillis(); 
     String result1 = String.format("for loop: %.5f", (end-start)/1000); 
     System.out.println(s1); 
     System.out.println(result1); 

     // do-while loop 
     double start1 = System.currentTimeMillis(); 
     int i = 0; 
     long s2 = 0; 
     do { 
      i++; 
      int j = 0; 
      do { 
       s2 += 1; 
       j++; 
      } while (j < L); 
     } while (i < L); 
     double end1 = System.currentTimeMillis(); 
     String result2 = String.format("do-while: %.5f", (end1-start1)/1000); 
     System.out.println(s2); 
     System.out.println(result2); 

     // while loop 
     double start2 = System.currentTimeMillis(); 
     i = 0; 
     long s3 = 0; 
     while (i < L) { 
      i++; 
      int j = 0; 
      while (j < L) { 
       s3 += 1; 
       j++; 
      } 
     } 
     double end2 = System.currentTimeMillis(); 
     String result3 = String.format("while: %.5f", (end2-start2)/1000); 
     System.out.println(s3); 
     System.out.println(result3); 
    } 
} 

所有循環的計數器總和爲100億;結果困擾我:

for循環:6.48300

做,而:0.41200

同時:9.71500

爲什麼do-while循環,以便更快?這種性能差距與對L的任何更改並行進行。我已經獨立運行這些循環,並且它們執行相同的操作。

+1

我無法重現您的號碼。 while循環對於我來說運行速度相同,for循環略慢。 – Mysticial 2013-04-25 03:53:52

+5

在任何情況下,這不是一個特別好的基準,因爲編譯器或JIT可能能夠完全移除內部循環。 – Mysticial 2013-04-25 03:55:07

+1

必須這樣 - 某種優化只能在do-while循環中執行。不過,我想知道更多關於這種機制的信息。 – JohnF 2013-04-25 04:09:52

回答

18

我已經運行您提供的代碼,也很驚訝地看到這些性能差異。好奇心帶領我開始調查並發現,儘管這些循環似乎在做同樣的事情,但它們之間還是有一些重要的區別。

我的這些循環的第一次運行後結果爲:

for loop: 1.43100 
do-while: 0.51300 
while: 1.54500 

但是當我運行這三個迴路至少10次,然後每個循環的性能幾乎相同。

for loop: 0.43200 
do-while: 0.46100 
while: 0.42900 

的JIT能夠隨着時間的推移,以優化這些循環,但必須有一些差異性導致這些環路有不同的初始性能。事實上實際上有兩個差異:

  • do-while循環比forwhile

爲了簡單起見做較少的比較假定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循環字節碼的存在負責這些循環之間的性能差異。