Domanda

Come si scrive (ed si 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 tempo/iterazione o iterazioni/tempo, e perché?

Imparentato: Il benchmarking del cronometro è accettabile?

È stato utile?

Soluzione

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

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

Regola 1: includi 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: esegui sempre con -XX:+PrintCompilation, -verbose:gc, ecc., in modo da poter verificare che il compilatore e altre parti della JVM non stiano svolgendo un lavoro imprevisto durante la fase di temporizzazione.

Regola 2.1: Stampa 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: sii consapevole della differenza tra -client e -server, OSR e compilazioni regolari. Il flag Trouble$1::run @ 2 (41 bytes) riporta le compilazioni OSR con un segno di at per indicare il punto di ingresso non iniziale, ad esempio: -Xbatch. Preferisci il server al client e regolarmente all'OSR, se stai cercando le migliori prestazioni.

Regola 4: fai 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: fai attenzione agli effetti di deoptimizzazione 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: usa gli strumenti appropriati per leggere la mente del compilatore e aspettati di essere sorpreso 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: riduci il rumore nelle tue misurazioni. Esegui il tuo benchmark su una macchina silenziosa ed eseguilo più volte, scartando i valori anomali. Utilizzare -XX:CICompilerCount=1 per serializzare il compilatore con l'applicazione e considerare l'impostazione Xmx per impedire al compilatore di funzionare in parallelo con se stesso. Fai del tuo meglio per ridurre il sovraccarico GC, impostare Xms (abbastanza grande) uguale a UseEpsilonGC e utilizzare <=> se è disponibile.

Regola 8: utilizza una libreria per il tuo benchmark in quanto probabilmente è più efficiente ed è già stato eseguito il debug per questo unico scopo. Come JMH , Caliper o Bill e Gli eccellenti benchmark UCSD di Paul per Java .

Altri suggerimenti

Le cose importanti per i benchmark Java sono:

  • Riscalda prima JIT eseguendo il codice più volte prima del cronometraggio esso
  • 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 ottenere un " clean " spazio di memoria con cui lavorare. (Sì, gc() è più un suggerimento che una garanzia, ma è molto probabile che raccoglierà davvero spazzatura nella mia esperienza.)
  • Mi piace visualizzare iterazioni e tempo e un punteggio di tempo / iterazione che può essere ridimensionato in modo tale che il " best " l'algoritmo ottiene un punteggio di 1,0 e gli altri vengono segnati in modo relativo. Ciò significa che è possibile eseguire gli algoritmi tutti per un periodo di tempo prolungato, variando sia il numero di iterazioni che il tempo, ma ottenendo comunque risultati comparabili.

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

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

jmh è un'imbracatura Java per creare, eseguire e analizzare benchmark nano/micro/macro scritti in Java e altri linguaggi destinati alla JVM.

Informazioni molto interessanti sepolte all'interno il campione testa i commenti.

Guarda anche:

  

Il benchmark dovrebbe misurare tempo / iterazione o iterazioni / tempo e perché?

Dipende da cosa stai provando a testare.

Se sei interessato a latenza , usa time / iteration e se sei interessato a throughput , usa iterations / time.

Assicurati di utilizzare in qualche modo i risultati calcolati nel codice sottoposto a benchmark.Altrimenti il ​​tuo codice può essere ottimizzato.

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

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

Esistono 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 è possibile fidarsi della precisione dei tempi misurati per intervalli molto brevi.

Terzo:La JVM ottimizza il codice durante l'esecuzione.Pertanto, esecuzioni diverse nella stessa istanza JVM diventeranno sempre più veloci.

I miei consigli:Fai in modo che il tuo benchmark venga eseguito per alcuni secondi, che è più affidabile di un tempo di esecuzione superiore ai millisecondi.Riscaldare la JVM (significa eseguire il benchmark almeno una volta senza misurare, in modo che la JVM possa eseguire ottimizzazioni).Ed esegui il tuo benchmark più volte (forse 5 volte) e prendi il valore medio.Esegui ogni micro-benchmark in una nuova istanza JVM (chiama per ogni benchmark nuovo Java), altrimenti gli effetti di ottimizzazione della JVM possono influenzare l'esecuzione successiva dei test.Non eseguire cose che non vengono eseguite nella fase di riscaldamento (in quanto ciò potrebbe innescare il caricamento della classe e la ricompilazione).

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

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.

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

Per aggiungere agli altri eccellenti 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 in uso, 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 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. Tieni presente che la temperatura e i volumi influenzano anche la 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 stai utilizzando System.nanoTime() per eseguire il benchmark di un determinato bit di codice, metti le chiamate all'assegnazione in luoghi 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 stai ricevendo immediatamente l'ora di fine al termine del codice. Invece, prova quanto segue:

final long endTime, startTime = System.nanoTime();
//code here...
endTime = System.nanoTime();
System.out.println("Code took "+(endTime-startTime)+"nano seconds");
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top