2015-09-16 15 views
6

Qual è il vantaggio di questa linea OpenJDK number 1455.Perché copiare un riferimento di campo a un locale prima di utilizzarlo in un ciclo?

Codice frammento:

private final char value[]; 
// ... 
public int hashCode() { 
    int h = hash; 
    if (h == 0 && value.length > 0) { 



     char val[] = value;  // <--- this line 



     for (int i = 0; i < value.length; i++) { 
      h = 31 * h + val[i]; 
     } 
     hash = h; 
    } 
    return h; 
} 

Si noti che, anche se un riferimento a private final char value[] viene copiato locale val per l'accesso all'interno del ciclo, il suo campo .length è ancora accessibile attraverso value, non val.

Sospetto "prestazioni" essere la risposta (ad esempio è più veloce da leggere dal locale che dal campo) ma apprezzerei una risposta precisa e di facile lettura, forse anche alcuni dati sul vantaggio.

+0

Certamente, penso che è ridondante e non riesce a capire perché. – hata

+0

Non c'è alcun vantaggio. Anche loro non ne stanno facendo una copia. Ha solo ulteriori riferimenti. Bello, hai scoperto questo 1. – rajuGT

+0

Chiunque apre una segnalazione di bug su questo? – lschuetze

risposta

3

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

Problemi correlati