Ho dato un'occhiata al bytecode, come @user ha commentato, probabilmente è un'ottimizzazione per evitare la chiamata getfield
all'interno del ciclo. Ma hanno incasinato e fanno ancora riferimento alla variabile di valore nella condizione di loop ... quindi questo effettivamente rende il bytecode più lungo e più lento.
public int h1() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
public int h2() {
int h = hash;
if (h == 0 && value.length > 0) {
for (int i = 0; i < value.length; i++) {
h = 31 * h + value[i];
}
hash = h;
}
return h;
}
Possiamo vedere che entrambi i metodi producono bytecode quasi identico, tuttavia la nostra implementazione "ottimizzato" in realtà finisce con altre 2 chiamate.
Nota come il test del ciclo for (righe 39-45 in h1 e righe 37-43 in h2) chiama getfield per eseguire la chiamata della lunghezza dell'array.
Bytecode:
public int h1();
Code:
0: aload_0
1: getfield #17 // Field hash:I
4: istore_1
5: iload_1
6: ifne 53
9: aload_0
10: getfield #15 // Field value:[C
13: arraylength
14: ifle 53
17: aload_0
18: getfield #15 // Field value:[C
21: astore_2
22: iconst_0
23: istore_3
24: goto 39
27: bipush 31
29: iload_1
30: imul
31: aload_2
32: iload_3
33: caload
34: iadd
35: istore_1
36: iinc 3, 1
39: iload_3
40: aload_0
41: getfield #15 // Field value:[C
44: arraylength
45: if_icmplt 27
48: aload_0
49: iload_1
50: putfield #17 // Field hash:I
53: iload_1
54: ireturn
public int h2();
Code:
0: aload_0
1: getfield #17 // Field hash:I
4: istore_1
5: iload_1
6: ifne 51
9: aload_0
10: getfield #15 // Field value:[C
13: arraylength
14: ifle 51
17: iconst_0
18: istore_2
19: goto 37
22: bipush 31
24: iload_1
25: imul
26: aload_0
27: getfield #15 // Field value:[C
30: iload_2
31: caload
32: iadd
33: istore_1
34: iinc 2, 1
37: iload_2
38: aload_0
39: getfield #15 // Field value:[C
42: arraylength
43: if_icmplt 22
46: aload_0
47: iload_1
48: putfield #17 // Field hash:I
51: iload_1
52: ireturn
Se avevano cambiato la condizione del ciclo di utilizzare anche il campo locale,
...
for (int i = 0; i < val.length; i++) {
...
Poi il bytecode realtà diventa più breve e perde la chiamata probabilmente più lento GetField in the loop,
public int h1();
Code:
0: aload_0
1: getfield #17 // Field hash:I
4: istore_1
5: iload_1
6: ifne 50
9: aload_0
10: getfield #15 // Field value:[C
13: arraylength
14: ifle 50
17: aload_0
18: getfield #15 // Field value:[C
21: astore_2
22: iconst_0
23: istore_3
24: goto 39
27: bipush 31
29: iload_1
30: imul
31: aload_2
32: iload_3
33: caload
34: iadd
35: istore_1
36: iinc 3, 1
39: iload_3
40: aload_2
41: arraylength
42: if_icmplt 27
45: aload_0
46: iload_1
47: putfield #17 // Field hash:I
50: iload_1
51: ireturn
Fare uno stupido test del mio metodo jdk 1.7.0_79 solo ripetendo il metodo un paio di volte mostra costantemente il metodo originale circa 5 volte più lungo per eseguire quindi non ottimizzato o correttamente ottimizzato metodi. Quest'ultimo 2 non ha alcuna differenza nelle prestazioni.
Quindi, immagino, in conclusione, la memorizzazione del campo a livello locale è stato un tentativo di ottimizzazione dei metodi bytecode, probabilmente prima che il jit fosse in grado di ottimizzare esso stesso, ma lo hanno incasinato e in realtà reso il metodo molto peggiore ...
Questo codice è in realtà fissato in Java 9 per, https://bugs.openjdk.java.net/browse/JDK-8058643
Certamente, penso che è ridondante e non riesce a capire perché. – hata
Non c'è alcun vantaggio. Anche loro non ne stanno facendo una copia. Ha solo ulteriori riferimenti. Bello, hai scoperto questo 1. – rajuGT
Chiunque apre una segnalazione di bug su questo? – lschuetze