Come scrivo un micro-benchmark corretto in Java?
Come scrivi (ed esegui) un micro-benchmark corretto in Java?
Sto cercando alcuni esempi di codice e commenti che illustrino varie cose a cui pensare.
Esempio: il benchmark deve misurare tempo / iterazione o iterazioni / tempo e perché?
Correlati: il benchmarking del cronometro è accettabile?
Risposte
Suggerimenti sulla scrittura di micro benchmark dai creatori di Java HotSpot :
Regola 0: leggi un documento attendibile su JVM e micro-benchmarking. Uno bravo è Brian Goetz, 2005 . Non aspettarti troppo dai micro-benchmark; misurano solo una gamma limitata di caratteristiche prestazionali JVM.
Regola 1: includi sempre una fase di riscaldamento che esegue il kernel di prova fino in fondo, sufficiente per attivare tutte le inizializzazioni e le compilazioni prima delle fasi di temporizzazione. (Un numero inferiore di iterazioni va bene nella fase di riscaldamento. La regola pratica è di diverse decine di migliaia di iterazioni del ciclo interno.)
Regola 2: Sempre correre con -XX:+PrintCompilation
, -verbose:gc
e così via, in modo da poter verificare che il compilatore e altre parti della JVM non stanno facendo il lavoro imprevisto durante la fase di sincronizzazione.
Regola 2.1: Stampa messaggi all'inizio e alla fine delle fasi di cronometraggio e riscaldamento, in modo da poter verificare che non vi sia alcun output dalla Regola 2 durante la fase di cronometraggio.
Regola 3: essere consapevoli della differenza tra -client
e -server
, e OSR e le compilazioni regolari. La -XX:+PrintCompilation
bandiera riporta compilation OSR con un at-segno per indicare il punto di ingresso non iniziale, per esempio: Trouble$1::run @ 2 (41 bytes)
. Preferisci il server al client e regolare a OSR, se stai cercando le migliori prestazioni.
Regola 4: essere consapevoli degli effetti dell'inizializzazione. Non stampare per la prima volta durante la fase di temporizzazione, poiché la stampa carica e inizializza le classi. Non caricare nuove classi al di fuori della fase di riscaldamento (o fase di reporting finale), a meno che non si stia testando in modo specifico il caricamento delle classi (e in tal caso si caricano solo le classi di test). La regola 2 è la tua prima linea di difesa contro tali effetti.
Regola 5: essere consapevoli degli effetti di deottimizzazione e ricompilazione. Non prendere alcun percorso di codice per la prima volta nella fase di temporizzazione, perché il compilatore potrebbe spazzare via e ricompilare il codice, sulla base di un precedente presupposto ottimistico che il percorso non sarebbe stato utilizzato affatto. La regola 2 è la tua prima linea di difesa contro tali effetti.
Regola 6: usa strumenti appropriati per leggere la mente del compilatore e aspettati di essere sorpreso dal codice che produce. Ispeziona tu stesso il codice prima di formulare teorie su ciò che rende qualcosa più veloce o più lento.
Regola 7: ridurre il rumore nelle misurazioni. Esegui il benchmark su una macchina silenziosa ed eseguilo più volte, eliminando i valori anomali. Utilizzare -Xbatch
per serializzare il compilatore con l'applicazione e valutare l'impostazione -XX:CICompilerCount=1
per evitare che il compilatore venga eseguito in parallelo con se stesso. Fai del tuo meglio per ridurre l'overhead GC, impostare Xmx
(abbastanza grande) uguale Xms
e utilizzare UseEpsilonGCse disponibile.
Regola 8: utilizzare una libreria per il benchmark poiché è probabilmente più efficiente ed è già stata sottoposta a debug per questo unico scopo. Come JMH , Caliper o gli eccellenti benchmark UCSD di Bill e Paul per Java .
So che questa domanda è stata contrassegnata come risposta, ma volevo menzionare due librerie che ci aiutano a scrivere micro benchmark
Caliper di Google
Tutorial per iniziare
- http://codingjunkie.net/micro-benchmarking-with-caliper/
- http://vertexlabs.co.uk/blog/caliper
JMH di OpenJDK
Tutorial per iniziare
- Evitare le insidie del benchmarking sulla JVM
- Utilizzo di JMH per Java Microbenchmarking
- Introduzione a JMH
Le cose importanti per i benchmark Java sono:
- Scaldare il JIT prima eseguendo il codice più volte prima di cronometraggio che
- Assicurati di eseguirlo abbastanza a lungo da poter misurare i risultati in secondi o (meglio) decine di secondi
- Sebbene non sia possibile chiamare
System.gc()
tra le iterazioni, è una buona idea eseguirlo tra i test, in modo che ogni test possa avere uno spazio di memoria "pulito" con cui lavorare. (Sì,gc()
è più un suggerimento che una garanzia, ma è molto probabile che in base alla mia esperienza possa davvero raccogliere i rifiuti.) - Mi piace visualizzare le iterazioni e il tempo e un punteggio di tempo / iterazione che può essere scalato in modo tale che l'algoritmo "migliore" ottenga un punteggio di 1.0 e gli altri siano valutati in modo relativo. Ciò significa che puoi eseguire tutti gli algoritmi per un tempo molto lungo, variando sia il numero di iterazioni che il tempo, ma ottenendo comunque risultati comparabili.
Sto solo scrivendo un blog sulla progettazione di un framework di benchmarking in .NET. Ho un paio di post precedenti che potrebbero darti alcune idee - non tutto sarà appropriato, ovviamente, ma alcuni potrebbero esserlo.
jmh è una recente aggiunta a OpenJDK ed è stato scritto da alcuni ingegneri delle prestazioni di Oracle. Sicuramente vale la pena dare un'occhiata.
Jmh è un cablaggio Java per la creazione, l'esecuzione e l'analisi di benchmark nano / micro / macro scritti in Java e in altri linguaggi mirati alla JVM.
Informazioni molto interessanti sepolte nei commenti dei test di esempio .
Guarda anche:
- Evitare le insidie del benchmarking sulla JVM
- Discussione sui principali punti di forza di jmh .
Il benchmark dovrebbe misurare tempo / iterazione o iterazioni / tempo e perché?
Dipende da cosa stai cercando di testare.
Se sei interessato alla latenza , usa time / iteration e se sei interessato al throughput , usa iterations / time.
Se stai cercando di confrontare due algoritmi, esegui almeno due benchmark per ciascuno, alternando l'ordine. cioè:
for(i=1..n)
alg1();
for(i=1..n)
alg2();
for(i=1..n)
alg2();
for(i=1..n)
alg1();
Ho riscontrato alcune differenze evidenti (5-10% a volte) nel runtime dello stesso algoritmo in passaggi diversi ..
Inoltre, assicurati che n sia molto grande, in modo che il tempo di esecuzione di ogni ciclo sia di almeno 10 secondi circa. Più iterazioni, più cifre significative nel tempo di riferimento e più affidabili sono i dati.
Assicurati di utilizzare in qualche modo i risultati calcolati nel codice di benchmark. Altrimenti il tuo codice può essere ottimizzato.
Ci sono molte possibili insidie per la scrittura di micro-benchmark in Java.
Primo: devi calcolare con tutti i tipi di eventi che richiedono tempo più o meno casuale: Garbage collection, effetti di caching (del sistema operativo per i file e della CPU per la memoria), IO ecc.
Secondo: non puoi fidarti dell'accuratezza dei tempi misurati per intervalli molto brevi.
Terzo: la JVM ottimizza il codice durante l'esecuzione. Quindi esecuzioni diverse nella stessa istanza JVM diventeranno sempre più veloci.
I miei consigli: fai in modo che il tuo benchmark venga eseguito alcuni secondi, che è più affidabile di un runtime su millisecondi. Riscaldare la JVM (significa eseguire il benchmark almeno una volta senza misurare, che la JVM può eseguire le ottimizzazioni). Ed esegui il tuo benchmark più volte (forse 5 volte) e prendi il valore mediano. Esegui ogni micro-benchmark in una nuova istanza JVM (chiama per ogni benchmark un nuovo Java) altrimenti gli effetti di ottimizzazione della JVM possono influenzare i test successivi. Non eseguire cose che non vengono eseguite nella fase di riscaldamento (in quanto ciò potrebbe attivare il caricamento della classe e la ricompilazione).
Va anche notato che potrebbe anche essere importante analizzare i risultati del micro benchmark quando si confrontano diverse implementazioni. Pertanto dovrebbe essere effettuato un test di significatività .
Questo perché l'implementazione A
potrebbe essere più veloce durante la maggior parte delle esecuzioni del benchmark rispetto all'implementazione B
. Ma A
potrebbe anche avere uno spread più elevato, quindi il vantaggio in termini di prestazioni misurate A
non avrà alcun significato se confrontato con B
.
Quindi è anche importante scrivere ed eseguire correttamente un micro benchmark, ma anche analizzarlo correttamente.
Per aggiungere agli altri ottimi consigli, vorrei anche tenere presente quanto segue:
Per alcune CPU (ad esempio la gamma Intel Core i5 con TurboBoost), la temperatura (e il numero di core attualmente utilizzati, nonché la loro percentuale di utilizzo) influisce sulla velocità di clock. Poiché le CPU hanno un clock dinamico, ciò può influire sui risultati. Ad esempio, se si dispone di un'applicazione a thread singolo, la velocità di clock massima (con TurboBoost) è superiore a quella di un'applicazione che utilizza tutti i core. Ciò può quindi interferire con il confronto delle prestazioni a thread singolo e multi-thread su alcuni sistemi. Tieni presente che la temperatura e le volatilità influiscono anche sulla durata del mantenimento della frequenza turbo.
Forse un aspetto più fondamentale su cui hai il controllo diretto: assicurati di misurare la cosa giusta! Ad esempio, se stai usando System.nanoTime()
per confrontare un particolare bit di codice, metti le chiamate all'assegnazione in posti che abbiano senso per evitare di misurare cose che non ti interessano. Ad esempio, non fare:
long startTime = System.nanoTime();
//code here...
System.out.println("Code took "+(System.nanoTime()-startTime)+"nano seconds");
Il problema è che non ottieni immediatamente l'ora di fine quando il codice è terminato. Prova invece quanto segue:
final long endTime, startTime = System.nanoTime();
//code here...
endTime = System.nanoTime();
System.out.println("Code took "+(endTime-startTime)+"nano seconds");
http://opt.sourceforge.net/Java Micro Benchmark: attività di controllo necessarie per determinare le caratteristiche di prestazioni comparative del sistema informatico su piattaforme diverse. Può essere utilizzato per guidare le decisioni di ottimizzazione e per confrontare diverse implementazioni Java.