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.
Non riesco a riprodurre i tuoi numeri. Entrambi i loop eseguono la stessa velocità con il ciclo for leggermente più lento. – Mysticial
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
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