2015-09-16 13 views
6

Was ist der Vorteil dieser OpenJDK Linie number 1455.Warum kopieren Sie einen Feldverweis auf einen lokalen, bevor Sie ihn in einer Schleife verwenden?

Code-Snippet:

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

Beachten Sie, dass, obwohl ein Verweis auf private final char value[] auf die lokalen val für den Zugriff innerhalb der Schleife kopiert wird, sein .length Feld noch durch value zugegriffen wird, nicht val.

Ich vermute "Leistung", um die Antwort zu sein (zB ist es schneller aus dem lokalen als aus dem Feld zu lesen), aber ich würde eine präzise und leicht zu lesen Antwort, vielleicht sogar einige Daten über die Vorteil.

+0

Sicher, ich denke, es ist überflüssig und kann nicht verstehen, warum. – hata

+0

Es gibt keinen Vorteil. Selbst sie machen keine Kopie davon. Es hat nur zusätzliche Referenz. Schön, das hast du herausgefunden 1. – rajuGT

+0

Wer öffnet einen Fehlerbericht? – lschuetze

Antwort

3

Ich habe mir den Bytecode angeschaut, wie @user kommentierte, es ist wahrscheinlich eine Optimierung, um den getfield Aufruf innerhalb der Schleife zu vermeiden. Aber sie haben versagt und verweisen immer noch auf die Wertvariable in der Schleifenbedingung ... so macht dies den Bytecode tatsächlich länger und langsamer.

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

Wir können sehen, dass beide Methoden fast identisch Bytecode erzeugen, aber unsere „optimiert“ Implementierung endet eigentlich 2 weitere Anrufe auf verwenden.

Beachten Sie, wie der for-Loop-Test (Zeilen 39-45 in h1 und Zeilen 37-43 in h2) getfield aufruft, um den Aufruf der Array-Länge auszuführen.

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 

Wenn sie die Schleifenbedingung auch geändert hatte, um das lokale Feld zu verwenden,

... 
for (int i = 0; i < val.length; i++) { 
... 

Dann wird der Bytecode tatsächlich wird kürzer und verliert den wohl langsamer getfield Anruf in die Schleife,

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  

Ein dummer Test, nur einige Male über die Methode zu schlendern, auf meiner jdk 1.7.0_79 zeigt immer wieder, dass die ursprüngliche Methode urkomisch ungefähr 5 mal länger dauert, um dann entweder die un "optimierte" oder die richtig "optimierte" zu laufen Methoden. Die letzteren 2 haben keinen Unterschied in der Leistung.

Also abschließend denke ich, Speichern des Feldes lokal war ein Versuch, die Methoden Bytecode zu optimieren, wahrscheinlich bevor der Jit konnte dies selbst zu optimieren, aber sie vermasselt es und tatsächlich machte die Methode viel schlimmer ...

Dieser Code ist in Java 9 pro tatsächlich fixiert, https://bugs.openjdk.java.net/browse/JDK-8058643

Verwandte Themen