2016-06-23 49 views
9

Perché lo snippet di codice A 14x è più lento dello snippet di codice B?
(testato con jdk1.8.0_60 su Windows 7 64bit)Perché la finale statica è più lenta di una nuova su ciascuna iterazione

frammento di codice 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); 
    } 
} 

Codice frammento 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: Utilizzando la La parola chiave new all'interno di un ciclo è più veloce dell'accesso a un campo static final.

(nota: rimozione della parola final su RECTANGLE non modifica il tempo di esecuzione)

+6

Il test non tiene conto del tempo di avvio/avvio della JVM. I risultati saranno probabilmente incoerenti come scritti. Stai davvero testando la velocità di avvio della JVM, quindi esegui il tuo codice. – SnakeDoc

+0

@SnakeDoc, riscaldamento/avvio JVM è certamente una considerazione, ma non spiega la differenza di prestazioni che vedo per i codici dell'OP. Anche dopo aver inserito un ciclo di riscaldamento in quello più lento, le prestazioni (migliorate) non si avvicinano a quelle più veloci. –

+0

no, l'ho provato eseguendo il caso A, quindi il caso B all'interno di un'applicazione e quindi invertendo l'ordine, in entrambi i casi il caso A era 21x più lungo –

risposta

15

Nel primo caso (statica finale) JVM deve leggere campi oggetto dalla memoria. Nel secondo caso i valori sono noti per essere costanti. Inoltre, poiché l'oggetto non esce dal loop, l'allocazione viene eliminata, ad es. i suoi campi sono sostituiti con variabili locali.

Il seguente JMH benchmark sostiene la teoria:

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

Risultati:

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 è veloce come restituire un costante, perché allocazione oggetto viene eliminato e il hashCode è costante ripiegato durante Compilazione JIT.

Quando l'ottimizzazione EliminateAllocations è disabilitata, il tempo di benchmark è notevolmente più alto, ma i calcoli aritmetici di hashCode sono ancora piegati in modo costante.

Nell'ultimo benchmark, anche se RECTANGLE è dichiarato definitivo, i suoi campi potrebbero essere in teoria modificati, quindi JIT non può eliminare l'accesso al campo.

+0

Per _constant-folded_, si intende che i vari campi 'RoundRectangle2D.Double' vengono estratti in variabili locali e il' hashCode' viene calcolato su quelli, alla fine capendo che restituisce sempre lo stesso valore? Presumibilmente, si può rompere tutto questo cambiando uno degli argomenti del costruttore come variabile calcolata (che si valuta a 2 per il gusto di questo esempio)? –

+0

Quindi, nell'ultimo benchmark, l'unico motivo per cui non può essere ottimizzato è perché le modifiche ai campi di RECTANGLE potrebbero essere eseguite da un altro thread? – qwertzguy

+0

@SotiriosDelimanolis Sì, JIT comprende che tutti gli operandi dell'espressione sono costanti, quindi anche l'espressione è costante. Se si aggiunge una variabile, l'espressione verrà calcolata in fase di esecuzione, ma ciò non richiederà necessariamente carichi di memoria. – apangin