2016-06-23 6 views
9

Warum Code ist ein 14x langsamer als Code-Schnipsel B-Snippet?
(getestet mit jdk1.8.0_60 auf Windows 7 64-Bit)Warum ist static final langsamer als eine neue auf jeder Iteration

Code-Snippet A:

import java.awt.geom.RoundRectangle2D; 

public class Test { 
    private static final RoundRectangle2D.Double RECTANGLE = new RoundRectangle2D.Double(1, 2, 3, 4, 5, 6); 

    public static void main(String[] args) { 
     int result = RECTANGLE.hashCode(); 
     long start = System.nanoTime(); 
     for (int i = 0; i < 100_000_000; i++) { 
      result += RECTANGLE.hashCode();   // <= Only change is on this line 
     } 
     System.out.println((System.nanoTime() - start)/1_000_000); 
     System.out.println(result); 
    } 
} 

-Code-Schnipsel B:

import java.awt.geom.RoundRectangle2D; 

public class Test { 
    private static final RoundRectangle2D.Double RECTANGLE = new RoundRectangle2D.Double(1, 2, 3, 4, 5, 6); 

    public static void main(String[] args) { 
     int result = RECTANGLE.hashCode(); 
     long start = System.nanoTime(); 
     for (int i = 0; i < 100_000_000; i++) { 
      result += new RoundRectangle2D.Double(1, 2, 3, 4, 5, 6).hashCode(); 
     } 
     System.out.println((System.nanoTime() - start)/1_000_000); 
     System.out.println(result); 
    } 
} 

TL; DR: die Verwendung von new Schlüsselwort innerhalb einer Schleife ist schneller als ein static final Feld zugreifen.

(Anmerkung: Das Entfernen der final Schlüsselwort auf RECTANGLE nicht die Ausführungszeit ändern)

+6

Ihre Tests berücksichtigen nicht die JVM Aufwärmzeit/Startzeit. Ihre Ergebnisse werden wahrscheinlich inkonsistent wie geschrieben sein. Sie testen wirklich, wie schnell die JVM startet und führt dann Ihren Code aus. – SnakeDoc

+0

@SnakeDoc, JVM Warm-up/Start ist sicherlich eine Überlegung, aber es erklärt nicht den Leistungsunterschied, den ich für die Codes des OP sehe. Selbst nach dem Einführen einer Aufwärmschleife in die langsamere, kommt die (verbesserte) Leistung nicht annähernd schneller voran. –

+0

nein, ich versuchte, dies von Fall zu laufen A dann Fall B innerhalb einer Anwendung und dann die Reihenfolge umgekehrt wird, eine in beiden Fällen war Fall A 21x länger –

Antwort

15

Im ersten Fall (static final) JVM benötigt Objektfelder aus dem Speicher zu lesen. Im zweiten Fall ist bekannt, dass die Werte konstant sind. Da das Objekt nicht aus der Schleife austritt, wird außerdem die Zuweisung eliminiert, z. Seine Felder werden durch lokale Variablen ersetzt.

Die folgende JMH Benchmark unterstützt die Theorie:

package bench; 

import org.openjdk.jmh.annotations.*; 
import java.awt.geom.RoundRectangle2D; 

@State(Scope.Benchmark) 
public class StaticRect { 
    private static final RoundRectangle2D.Double RECTANGLE = 
      new RoundRectangle2D.Double(1, 2, 3, 4, 5, 6); 

    @Benchmark 
    public long baseline() { 
     return 0; 
    } 

    @Benchmark 
    public long testNew() { 
     return new RoundRectangle2D.Double(1, 2, 3, 4, 5, 6).hashCode(); 
    } 

    @Benchmark 
    @Fork(jvmArgs = "-XX:-EliminateAllocations") 
    public long testNewNoEliminate() { 
     return new RoundRectangle2D.Double(1, 2, 3, 4, 5, 6).hashCode(); 
    } 

    @Benchmark 
    public int testStatic() { 
     return RECTANGLE.hashCode(); 
    } 
} 

Ergebnisse:

Benchmark      Mode Cnt Score Error Units 
StaticRect.baseline   avgt 10 2,840 ± 0,048 ns/op 
StaticRect.testNew    avgt 10 2,831 ± 0,011 ns/op 
StaticRect.testNewNoEliminate avgt 10 8,566 ± 0,036 ns/op 
StaticRect.testStatic   avgt 10 12,689 ± 0,057 ns/op 

testNew ist so schnell, wie eine konstante Rückkehr weil Zuordnungsobjekt eliminiert wird und die hashCode konstant falteten während JIT-Zusammenstellung.

Wenn EliminateAllocations Optimierung deaktiviert ist, ist der Maßstab der Zeit deutlich höher, aber die arithmetischen Berechnungen von hashCode sind noch Konstant gefaltet.

In der letzten Benchmark, obwohl RECTANGLE als final deklariert ist, können seine Felder theoretisch geändert werden, sodass JIT den Feldzugriff nicht eliminieren kann.

+0

Mit _constant-folded_ meinen Sie, dass die verschiedenen 'RoundRectangle2D.Double'-Felder in lokale Variablen extrahiert werden und der 'hashCode' daraus berechnet wird. schließlich herausfinden, dass es immer den gleichen Wert zurückgibt? Vermutlich könnten Sie all dies durchbrechen, indem Sie eines der Konstruktorargumente als eine berechnete Variable ändern (die aus diesem Beispiel 2 ergibt)? –

+0

Also im letzten Benchmark, der einzige Grund, warum es nicht optimieren kann, ist, weil Änderungen an RECTANGLE-Feldern von einem anderen Thread vorgenommen werden könnten? – qwertzguy

+0

@StiriosDelimanolis Ja, JIT erkennt, dass alle Operanden des Ausdrucks konstant sind, daher ist der Ausdruck auch konstant. Wenn Sie eine Variable hinzufügen, wird der Ausdruck zur Laufzeit berechnet, dies erfordert jedoch nicht unbedingt Speicherauslastung. – apangin

Verwandte Themen