Come faccio a scrivere un micro-benchmark corretto in Java?


870

Come si scrive (ed esegue) un micro-benchmark corretto in Java?

Sto cercando alcuni esempi di codice e commenti che illustrino varie cose a cui pensare.

Esempio: il benchmark dovrebbe misurare il tempo / iterazione o iterazioni / tempo e perché?

Correlati: il benchmarking del cronometro è accettabile?


Vedi [questa domanda] [1] di qualche minuto fa per alcune informazioni correlate. modifica: scusa, questa non dovrebbe essere una risposta. Avrei dovuto pubblicare come commento. [1]: stackoverflow.com/questions/503877/…
Tiago,

È stato dopo aver pianificato di riferire il poster di quella domanda a una domanda come questa che ho notato che questa domanda non esisteva. Quindi eccolo qua, spero che col tempo creerà alcuni buoni consigli.
John Nilsson,

5
Java 9 potrebbe fornire alcune funzionalità per il micro-benchmarking: openjdk.java.net/jeps/230
Raedwald

1
@Raedwald Penso che JEP abbia l'obiettivo di aggiungere alcuni micro benchmark al codice JDK, ma non credo che jmh sarà incluso nel JDK ...
assylias,

1
@Raedwald Ciao dal futuro. Non ha fatto il taglio .
Michael,

Risposte:


787

Suggerimenti sulla scrittura di micro benchmark dai creatori di Java HotSpot :

Regola 0: leggi un documento affidabile su JVM e micro-benchmarking. Una buona è Brian Goetz, 2005 . Non aspettarti troppo dai micro-benchmark; misurano solo una gamma limitata di caratteristiche prestazionali JVM.

Regola 1: includere sempre una fase di riscaldamento che esegua il kernel di test fino in fondo, abbastanza da innescare tutte le inizializzazioni e le compilazioni prima delle fasi di temporizzazione. (Meno iterazioni sono OK nella fase di riscaldamento. La regola empirica è diverse decine di migliaia di iterazioni del ciclo interno.)

Regola 2: eseguire sempre con -XX:+PrintCompilation, -verbose:gcecc., In modo da poter verificare che il compilatore e le altre parti della JVM non stiano eseguendo operazioni impreviste durante la fase di temporizzazione.

Regola 2.1: stampare i messaggi all'inizio e alla fine delle fasi di temporizzazione e riscaldamento, in modo da poter verificare che non vi sia alcun output dalla Regola 2 durante la fase di temporizzazione.

Regola 3: essere consapevoli della differenza tra -cliente -server, e OSR e compilazioni regolari. La -XX:+PrintCompilationbandiera 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 regolarmente all'OSR, se stai cercando le migliori prestazioni.

Regola 4: prestare attenzione agli effetti di 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 report finale), a meno che non si stia testando il caricamento specifico della classe (e in tal caso caricare 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, poiché il compilatore potrebbe spazzare 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: utilizzare gli strumenti appropriati per leggere la mente del compilatore e aspettarsi di essere sorpresi dal codice che produce. Ispeziona tu stesso il codice prima di formare teorie su ciò che rende qualcosa più veloce o più lento.

Regola 7: ridurre il rumore nelle misurazioni. Esegui il tuo benchmark su una macchina silenziosa ed eseguilo più volte, scartando i valori anomali. Utilizzare -Xbatchper serializzare il compilatore con l'applicazione e considerare l'impostazione -XX:CICompilerCount=1per impedire al compilatore di funzionare in parallelo con se stesso. Fai del tuo meglio per ridurre il sovraccarico GC, impostare Xmxuguali (abbastanza grandi) Xmse utilizzare UseEpsilonGCse è disponibile.

Regola 8: utilizzare una libreria per il benchmark poiché è probabilmente più efficiente ed è già stato eseguito il debug per questo unico scopo. Come JMH , Caliper o Bill e Paul's Excellent UCSD Benchmarks for Java .


5
Questo è stato anche un articolo interessante: ibm.com/developerworks/java/library/j-jtp12214
John Nilsson

143
Inoltre, non usare mai System.currentTimeMillis () a meno che non si stia bene con una precisione di + o - 15 ms, tipica della maggior parte delle combinazioni di OS + JVM. Utilizzare invece System.nanoTime ().
Scott Carey,


94
Va notato che System.nanoTime()non è garantito che sia più preciso di System.currentTimeMillis(). È garantito solo per essere altrettanto accurato. Di solito è sostanzialmente più preciso, tuttavia.
Gravità,

41
Il motivo principale per cui uno deve usare System.nanoTime()invece di System.currentTimeMillis()è che il primo è garantito per essere monotonicamente crescente. Sottraendo i valori restituiti due currentTimeMillisinvocazioni possono effettivamente dare risultati negativi, probabilmente perché l'ora del sistema è stata regolata da un demone NTP.
Waldheinz,

239

So che questa domanda è stata contrassegnata come risposta, ma volevo menzionare due librerie che ci aiutano a scrivere micro benchmark

Calibro di Google

Tutorial introduttivi

  1. http://codingjunkie.net/micro-benchmarking-with-caliper/
  2. http://vertexlabs.co.uk/blog/caliper

JMH di OpenJDK

Tutorial introduttivi

  1. Evitare insidie ​​di benchmarking sulla JVM
  2. http://nitschinger.at/Using-JMH-for-Java-Microbenchmarking
  3. http://java-performance.info/jmh/

37
+1 avrebbe potuto essere aggiunto come Regola 8 della risposta accettata: Regola 8: poiché così tante cose possono andare storte, probabilmente dovresti usare una libreria esistente piuttosto che provare a farlo da solo!
assylias,

8
@Pangea jmh è probabilmente superiore a Caliper al giorno d'oggi, Vedi anche: groups.google.com/forum/#!msg/mechanical-sympathy/m4opvy4xq3U/…
assylias,

87

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 abbia uno spazio di memoria "pulito" su cui lavorare. (Sì, gc()è più un suggerimento che una garanzia, ma è molto probabile che raccoglierà spazzatura nella mia esperienza.)
  • Mi piace visualizzare iterazioni e tempo e un punteggio di tempo / iterazione che può essere ridimensionato in modo tale che l'algoritmo "migliore" ottenga un punteggio di 1,0 e che gli altri vengano assegnati in modo relativo. Ciò significa che è possibile eseguire tutti gli algoritmi per un tempo prolungato, variando sia il numero di iterazioni che il tempo, ma ottenendo comunque risultati comparabili.

Sono solo in procinto di blog sulla progettazione di un framework di benchmarking in .NET. Ho un paio di post precedenti che potrebbero essere in grado di darti alcune idee - non tutto sarà appropriato, ovviamente, ma alcuni potrebbero esserlo.


3
Minore nitpick: IMO "in modo che ogni test venga" dovrebbe essere "in modo che ogni test possa ottenere" poiché il primo dà l'impressione che la chiamata libera gc sempre la memoria inutilizzata.
Sanjay T. Sharma,

@ SanjayT.Sharma: Beh, l' intenzione è che lo faccia davvero. Sebbene non sia strettamente garantito, in realtà è un suggerimento piuttosto forte. Modifica per essere più chiaro.
Jon Skeet,

1
Non sono d'accordo con la chiamata a System.gc (). È un suggerimento, tutto qui. Nemmeno "si spera che faccia qualcosa". Non dovresti mai chiamarlo. Questa è programmazione, non arte.
Gyorgyabraham,

13
@gyabraham: Sì, è un suggerimento, ma è quello che di solito ho notato. Quindi, se non ti piace usare System.gc(), come proponi di minimizzare la garbage collection in un test a causa degli oggetti creati nei test precedenti? Sono pragmatico, non dogmatico.
Jon Skeet,

9
@gyabraham: non so cosa intendi per "grande fallback". Puoi elaborare e di nuovo: hai una proposta per ottenere risultati migliori? Ho detto esplicitamente che non è una garanzia ...
Jon Skeet,

48

jmh è una recente aggiunta a OpenJDK ed è stata scritta da alcuni tecnici delle prestazioni di Oracle. Sicuramente vale la pena dare un'occhiata.

Il jmh è un cablaggio Java per la costruzione, l'esecuzione e l'analisi di benchmark nano / micro / macro scritti in Java e in altre lingue destinate alla JVM.

Informazioni molto interessanti sepolte nei commenti dei test di esempio .

Guarda anche:


1
Vedi anche questo post sul blog: psy-lob-saw.blogspot.com/2013/04/… per dettagli su come iniziare con JMH.
Nitsan Wakart,

Cordiali saluti, JEP 230: Microbenchmark Suite è una proposta OpenJDK basata su questo progetto Java Microbenchmark Harness (JMH) . Non ha effettuato il taglio per Java 9 ma può essere aggiunto in seguito.
Basil Bourque,

23

Il benchmark dovrebbe misurare il 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 alla velocità effettiva , usa iterations / time.


16

Se stai cercando di confrontare due algoritmi, esegui almeno due benchmark per ciascuno, alternando l'ordine. vale a dire:

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 tempo di esecuzione dello stesso algoritmo in passaggi diversi ..

Inoltre, assicurati che n sia molto grande, in modo che l'autonomia di ciascun loop sia di almeno 10 secondi circa. Più iterazioni, cifre più significative nel tempo di riferimento e più affidabili sono i dati.


5
La modifica naturale dell'ordine influenza il tempo di esecuzione. Le ottimizzazioni JVM e gli effetti di memorizzazione nella cache funzioneranno qui. Meglio "riscaldare" l'ottimizzazione JVM, eseguire più esecuzioni e confrontare ogni test in una JVM diversa.
Mnementh,

15

Assicurati di utilizzare in qualche modo i risultati che sono calcolati in codice di riferimento. Altrimenti il ​​tuo codice può essere ottimizzato.


13

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 cache (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: JVM ottimizza il codice durante l'esecuzione. Quindi, corse diverse nella stessa istanza JVM diventeranno sempre più veloci.

I miei consigli: fai funzionare il tuo benchmark per alcuni secondi, il che è più affidabile di un tempo di esecuzione nell'arco di millisecondi. Riscaldare la JVM (significa eseguire il benchmark almeno una volta senza misurare, che la JVM può eseguire ottimizzazioni). Esegui il tuo benchmark più volte (forse 5 volte) e prendi il valore mediano. Esegui tutti i micro-benchmark in una nuova istanza JVM (chiama per ogni nuovo benchmark Java) altrimenti gli effetti di ottimizzazione della JVM possono influenzare i test successivi. Non eseguire cose che non vengono eseguite nella fase di riscaldamento (poiché ciò potrebbe innescare il caricamento di classe e la ricompilazione).


8

Va anche notato che potrebbe anche essere importante analizzare i risultati del micro benchmark quando si confrontano diverse implementazioni. Pertanto un test di significatività dovrebbe essere effettuato .

Questo perché l'implementazione Apotrebbe essere più veloce durante la maggior parte delle esecuzioni del benchmark rispetto all'implementazione B. Ma Apotrebbe anche avere uno spread più elevato, quindi il vantaggio prestazionale misurato Anon avrà alcun significato se confrontato conB .

Quindi è anche importante scrivere ed eseguire correttamente un micro benchmark, ma anche analizzarlo correttamente.


8

Per aggiungere agli altri eccellenti consigli, sarei anche consapevole di quanto segue:

Per alcune CPU (ad esempio la gamma Intel Core i5 con TurboBoost), la temperatura (e il numero di core attualmente in uso, così come 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 rispetto a un'applicazione che utilizza tutti i core. Ciò può quindi interferire con i confronti di prestazioni single e multi-thread su alcuni sistemi. Tenere presente che anche la temperatura e le volatilità influiscono sulla durata della frequenza Turbo.

Forse un aspetto di fondamentale importanza su cui hai il controllo diretto: assicurati di misurare la cosa giusta! Ad esempio, se si utilizza System.nanoTime()per eseguire il benchmark di un determinato bit di codice, collocare le chiamate all'assegnazione in punti opportuni per evitare di misurare cose a cui non si è interessati. Ad esempio, non fare:

long startTime = System.nanoTime();
//code here...
System.out.println("Code took "+(System.nanoTime()-startTime)+"nano seconds");

Il problema è che non stai ottenendo immediatamente l'ora di fine quando il codice è terminato. Invece, prova quanto segue:

final long endTime, startTime = System.nanoTime();
//code here...
endTime = System.nanoTime();
System.out.println("Code took "+(endTime-startTime)+"nano seconds");

Sì, è importante non eseguire lavori non correlati all'interno della regione a tempo, ma il tuo primo esempio va ancora bene. C'è solo una chiamata a println, non una riga di intestazione separata o qualcosa del genere, e System.nanoTime()deve essere valutato come il primo passo nella costruzione della stringa arg per quella chiamata. Non c'è niente che un compilatore possa fare con il primo che non possa fare con il secondo, e nessuno dei due li incoraggia nemmeno a fare un lavoro extra prima di registrare un tempo di arresto.
Peter Cordes,

7

http://opt.sourceforge.net/ Java Micro Benchmark: attività di controllo necessarie per determinare le caratteristiche comparative delle prestazioni del sistema informatico su piattaforme diverse. Può essere utilizzato per guidare le decisioni di ottimizzazione e confrontare diverse implementazioni Java.


2
Sembra solo confrontare l'hardware JVM +, non un pezzo arbitrario di codice Java.
Stefan L,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.