2013-04-25 12 views
10

Sto confrontando l'efficienza dei cicli nidificati for, while e do-while in Java, e ho trovato alcuni strani risultati che mi servono per capire.Efficienza ciclo 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); 
    } 
} 

Tutti i loop contatori rispettivi sommano a 10 miliardi; i risultati mi perplessi:

ciclo for: 6,48300

do-while: 0,41200

mentre: 9,71500

Perché il do-while in modo molto più veloce? Questo divario di prestazioni si riduce in parallelo con qualsiasi modifica a L. Ho eseguito questi cicli in modo indipendente e hanno lo stesso risultato.

+1

Non riesco a riprodurre i tuoi numeri. Entrambi i loop eseguono la stessa velocità con il ciclo for leggermente più lento. – Mysticial

+5

In ogni caso, questo non è un punto di riferimento particolarmente grande poiché il compilatore o il JIT potrebbero essere in grado di rimuovere completamente il ciclo interno. – Mysticial

+1

Questo deve essere il caso: una sorta di ottimizzazione che viene eseguita solo per il ciclo do-while. Tuttavia, mi piacerebbe saperne di più su questo meccanismo. – JohnF

risposta

18

Ho eseguito il codice che hai fornito e sono stato anche sorpreso di vedere queste differenze nelle prestazioni. Guidato dalla curiosità, ho iniziato a indagare e ho scoperto che, nonostante questi cicli, sembra che stiano facendo la stessa cosa, ci sono alcune importanti differenze tra loro.

miei risultati dopo la prima esecuzione di questi cicli sono stati:

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

Ma quando ho eseguito questi tre cicli di almeno 10 volte poi le prestazioni di ciascuno di questi ciclo era praticamente la stessa.

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

Il JIT è in grado di ottimizzare questi cicli nel corso del tempo, ma ci deve essere una certa dissomiglianza causando questi cicli di avere un diverso prestazioni iniziali. In realtà ci sono due differenze:

  • Il ciclo do-while sta facendo meno confronti rispetto alle for e while loop

Per semplicità assumere L = 1

long s1 = 0; 
for (int i=0; i < L; i++) { 
    for (int j = 0; j < L; j++) { 
     s1 += 1; 

anello esterno : 0 anello interno: 0 ciclo interno: 1 anello esterno: 1

4 confronti in totale

int i = 0; 
long s2 = 0; 
do { 
    i++; 
    int j = 0; 
    do { 
     s2 += 1; 
     j++; 
    } while (j < L); 
} while (i < L); 

ciclo interno: 1 anello esterno: 1

2 confronti in totale

  • Diversi bytecode generato

Ai fini di ulteriori indagini che hanno cambiato la vostra classe un po ', non impattare il modo in cui funziona.

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; 
    } 
} 

Poi compilato e invocato javap -c -s -private -l Loop per ottenere il bytecode.

Prima il bytecode di 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 

Ora il bytecode di whileLooP:

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 

per rendere l'output più leggibile ho aggiungere commenti che descrivono ciò che ogni istruzione non basato sul ‪Java bytecode instruction listings.

Se osservate da vicino, noterete che c'è una differenza importante tra questi due bytecode. Il ciclo while (lo stesso vale per il ciclo for) ha le istruzioni if ​​(istruzione if_icmplt) definite alla fine del bytecode. Il che significa che per controllare la condizione di uscita del primo ciclo deve essere invocato un goto alla riga 26 e analogamente un goto alla riga 17 per il secondo ciclo.

È possibile che questo bytecode è stata generata utilizzando javac 1.6.0_45 su Mac OS X.

Sommario

Credo che la diversa quantità di confronti più l'esistenza di istruzioni Goto nel tempo e per il ciclo bytecode è responsabile per la differenza di prestazioni tra questi cicli.