Wie schreibe ich einen korrekten Mikro-Benchmark in Java?
Wie schreibt (und führt) man einen korrekten Mikro-Benchmark in Java aus?
Ich suche nach Codebeispielen und Kommentaren, die verschiedene Dinge veranschaulichen, über die man nachdenken sollte.
Beispiel: Sollte der Benchmark Zeit / Iteration oder Iterationen / Zeit messen und warum?
Verwandte: Ist Stoppuhr-Benchmarking akzeptabel?
Antworten
Tipps zum Schreiben von Mikro-Benchmarks von den Entwicklern von Java HotSpot :
Regel 0: Lesen Sie ein seriöses Papier über JVMs und Mikro-Benchmarking. Ein guter ist Brian Goetz, 2005 . Erwarten Sie nicht zu viel von Mikro-Benchmarks. Sie messen nur einen begrenzten Bereich von JVM-Leistungsmerkmalen.
Regel 1: Schließen Sie immer eine Aufwärmphase ein, in der Ihr Testkernel vollständig ausgeführt wird, sodass alle Initialisierungen und Kompilierungen vor der Timing-Phase (n) ausgelöst werden. (In der Aufwärmphase sind weniger Iterationen in Ordnung. Als Faustregel gelten mehrere Zehntausend Iterationen der inneren Schleife.)
Regel 2: Immer lief mit -XX:+PrintCompilation
, -verbose:gc
etc., so dass Sie überprüfen können , dass der Compiler und andere Teile der JVM sind nicht unerwartet Arbeit während Taktphase zu tun.
Regel 2.1: Drucken Sie Nachrichten zu Beginn und am Ende der Timing- und Aufwärmphase, damit Sie überprüfen können, ob während der Timing-Phase keine Ausgabe von Regel 2 erfolgt.
Regel 3: Beachten Sie den Unterschied zwischen -client
und -server
, OSR und regelmäßigen Zusammenstellungen. Das -XX:+PrintCompilation
Flag meldet OSR-Kompilierungen mit einem At-Zeichen, um den nicht anfänglichen Einstiegspunkt zu kennzeichnen, zum Beispiel : Trouble$1::run @ 2 (41 bytes)
. Bevorzugen Sie Server gegenüber Client und regulär gegenüber OSR, wenn Sie die beste Leistung erzielen möchten.
Regel 4: Beachten Sie die Initialisierungseffekte. Drucken Sie während Ihrer Timing-Phase nicht zum ersten Mal, da beim Drucken Klassen geladen und initialisiert werden. Laden Sie keine neuen Klassen außerhalb der Aufwärmphase (oder der letzten Berichtsphase), es sei denn, Sie testen das Laden von Klassen speziell (und laden in diesem Fall nur die Testklassen). Regel 2 ist Ihre erste Verteidigungslinie gegen solche Effekte.
Regel 5: Beachten Sie die Deoptimierungs- und Neukompilierungseffekte. Nehmen Sie zum ersten Mal in der Timing-Phase keinen Codepfad, da der Compiler den Code möglicherweise verschmutzt und neu kompiliert, basierend auf einer früheren optimistischen Annahme, dass der Pfad überhaupt nicht verwendet werden würde. Regel 2 ist Ihre erste Verteidigungslinie gegen solche Effekte.
Regel 6: Verwenden Sie geeignete Tools, um die Gedanken des Compilers zu lesen, und lassen Sie sich von dem von ihm erzeugten Code überraschen. Überprüfen Sie den Code selbst, bevor Sie Theorien darüber aufstellen, was etwas schneller oder langsamer macht.
Regel 7: Reduzieren Sie das Rauschen bei Ihren Messungen. Führen Sie Ihren Benchmark auf einem leisen Computer aus und führen Sie ihn mehrmals aus, wobei Sie Ausreißer verwerfen. Verwenden Sie -Xbatch
diese Option, um den Compiler mit der Anwendung zu serialisieren, und ziehen Sie die Einstellung -XX:CICompilerCount=1
in Betracht , um zu verhindern, dass der Compiler parallel zu sich selbst ausgeführt wird. Versuchen Sie nach besten Kräften, den GC-Overhead zu reduzieren, setzen Sie Xmx
(groß genug) gleich Xms
und verwenden Sie ihn, UseEpsilonGCfalls verfügbar.
Regel 8: Verwenden Sie eine Bibliothek für Ihren Benchmark, da diese wahrscheinlich effizienter ist und bereits zu diesem alleinigen Zweck getestet wurde. Wie JMH , Caliper oder Bill und Pauls ausgezeichnete UCSD-Benchmarks für Java .
Ich weiß, dass diese Frage als beantwortet markiert wurde, aber ich wollte zwei Bibliotheken erwähnen, die uns beim Schreiben von Mikro-Benchmarks helfen
Bremssattel von Google
Erste Schritte Tutorials
- http://codingjunkie.net/micro-benchmarking-with-caliper/
- http://vertexlabs.co.uk/blog/caliper
JMH von OpenJDK
Erste Schritte Tutorials
- Vermeiden von Benchmarking-Fallstricken in der JVM
- Verwenden von JMH für Java Microbenchmarking
- Einführung in JMH
Wichtige Dinge für Java-Benchmarks sind:
- Erwärmen Sie die JIT zuerst, indem Sie den Code mehrmals ausführen, bevor Sie ihn zeitlich festlegen
- Stellen Sie sicher, dass Sie es lange genug ausführen, um die Ergebnisse in Sekunden oder (besser) zehn Sekunden messen zu können
- Sie können zwar nicht
System.gc()
zwischen Iterationen aufrufen , es ist jedoch eine gute Idee, sie zwischen Tests auszuführen, damit jeder Test hoffentlich einen "sauberen" Speicherplatz zum Arbeiten erhält. (Ja,gc()
ist eher ein Hinweis als eine Garantie, aber es ist sehr wahrscheinlich, dass sich meiner Erfahrung nach wirklich Müll ansammelt.) - Ich zeige gerne Iterationen und Zeit sowie eine Zeit- / Iterationsbewertung an, die so skaliert werden kann, dass der "beste" Algorithmus eine Bewertung von 1,0 erhält und andere relativ bewertet werden. Dies bedeutet, dass Sie alle Algorithmen über einen längeren Zeitraum ausführen können , wobei sowohl die Anzahl der Iterationen als auch die Zeit variiert werden, aber dennoch vergleichbare Ergebnisse erzielt werden.
Ich bin gerade dabei, über das Design eines Benchmarking-Frameworks in .NET zu bloggen. Ich habe ein bekam Paar von früheren Beiträgen der in der Lage sein, Ihnen einige Ideen zu geben - nicht alles wird angemessen sein, natürlich, aber ein Teil davon sein kann.
jmh ist eine neue Erweiterung von OpenJDK und wurde von einigen Performance-Ingenieuren von Oracle geschrieben. Auf jeden Fall einen Blick wert.
Das jmh ist ein Java-Harness zum Erstellen, Ausführen und Analysieren von Nano- / Mikro- / Makro-Benchmarks, die in Java und anderen Sprachen für die JVM geschrieben wurden.
Sehr interessante Informationen, die in den Kommentaren der Beispieltests vergraben sind .
Siehe auch:
- Vermeiden von Benchmarking-Fallstricken in der JVM
- Diskussion über die Hauptstärken von jmh .
Sollte der Benchmark Zeit / Iteration oder Iterationen / Zeit messen und warum?
Es hängt davon ab, was Sie testen möchten.
Wenn Sie an Latenz interessiert sind , verwenden Sie Zeit / Iteration und wenn Sie an Durchsatz interessiert sind , verwenden Sie Iterationen / Zeit.
Wenn Sie versuchen, zwei Algorithmen zu vergleichen, führen Sie jeweils mindestens zwei Benchmarks in abwechselnder Reihenfolge durch. dh:
for(i=1..n)
alg1();
for(i=1..n)
alg2();
for(i=1..n)
alg2();
for(i=1..n)
alg1();
Ich habe einige bemerkenswerte Unterschiede (manchmal 5-10%) in der Laufzeit des gleichen Algorithmus in verschiedenen Durchgängen festgestellt.
Stellen Sie außerdem sicher, dass n sehr groß ist, damit die Laufzeit jeder Schleife mindestens 10 Sekunden beträgt. Je mehr Iterationen, desto signifikanter die Zahlen in Ihrer Benchmark-Zeit und desto zuverlässiger sind diese Daten.
Stellen Sie sicher, dass Sie Ergebnisse verwenden, die in Benchmark-Code berechnet werden. Andernfalls kann Ihr Code entfernt werden.
Es gibt viele mögliche Fallstricke beim Schreiben von Mikro-Benchmarks in Java.
Erstens: Sie müssen mit allen Arten von Ereignissen rechnen, die mehr oder weniger zufällig Zeit benötigen: Speicherbereinigung, Caching-Effekte (von Betriebssystem für Dateien und von CPU für Speicher), E / A usw.
Zweitens: Sie können der Genauigkeit der gemessenen Zeiten für sehr kurze Intervalle nicht vertrauen.
Drittens: Die JVM optimiert Ihren Code während der Ausführung. So werden verschiedene Läufe in derselben JVM-Instanz immer schneller.
Meine Empfehlungen: Lassen Sie Ihren Benchmark einige Sekunden laufen, das ist zuverlässiger als eine Laufzeit über Millisekunden. Erwärmen Sie die JVM (bedeutet, dass der Benchmark mindestens einmal ohne Messung ausgeführt wird, damit die JVM Optimierungen ausführen kann). Führen Sie Ihren Benchmark mehrmals (möglicherweise fünfmal) aus und nehmen Sie den Medianwert. Führen Sie jeden Micro-Benchmark in einer neuen JVM-Instanz aus (fordern Sie für jeden Benchmark neues Java an), da sonst die Optimierungseffekte der JVM die späteren laufenden Tests beeinflussen können. Führen Sie keine Dinge aus, die nicht in der Aufwärmphase ausgeführt werden (da dies das Laden und Neukompilieren von Klassen auslösen könnte).
Es sollte auch beachtet werden, dass es auch wichtig sein kann, die Ergebnisse des Mikro-Benchmarks zu analysieren, wenn verschiedene Implementierungen verglichen werden. Daher sollte ein Signifikanztest durchgeführt werden.
Dies liegt daran, dass die Implementierung A
während der meisten Durchläufe des Benchmarks möglicherweise schneller ist als die Implementierung B
. Kann A
aber auch einen höheren Spread aufweisen, sodass der gemessene Leistungsvorteil von A
im Vergleich zu nicht von Bedeutung ist B
.
Daher ist es auch wichtig, einen Mikro-Benchmark korrekt zu schreiben und auszuführen, ihn aber auch korrekt zu analysieren.
Um den anderen ausgezeichneten Rat zu ergänzen, würde ich auch Folgendes beachten:
Bei einigen CPUs (z. B. Intel Core i5 mit TurboBoost) wirkt sich die Temperatur (und die Anzahl der derzeit verwendeten Kerne sowie deren prozentuale Auslastung) auf die Taktrate aus. Da CPUs dynamisch getaktet werden, kann dies Ihre Ergebnisse beeinflussen. Wenn Sie beispielsweise eine Single-Threaded-Anwendung haben, ist die maximale Taktrate (mit TurboBoost) höher als bei einer Anwendung mit allen Kernen. Dies kann daher den Vergleich der Einzel- und Multithread-Leistung auf einigen Systemen beeinträchtigen. Beachten Sie, dass die Temperatur und die Spannungen auch die Dauer der Turbofrequenz beeinflussen.
Vielleicht ein grundlegenderer Aspekt, über den Sie die direkte Kontrolle haben: Stellen Sie sicher, dass Sie das Richtige messen! Wenn Sie beispielsweise System.nanoTime()
ein bestimmtes Codebit als Benchmark verwenden, platzieren Sie die Aufrufe der Zuweisung an Orten, die sinnvoll sind, um zu vermeiden, dass Dinge gemessen werden, an denen Sie nicht interessiert sind. Führen Sie beispielsweise Folgendes nicht aus:
long startTime = System.nanoTime();
//code here...
System.out.println("Code took "+(System.nanoTime()-startTime)+"nano seconds");
Das Problem ist, dass Sie nicht sofort die Endzeit erhalten, wenn der Code fertig ist. Versuchen Sie stattdessen Folgendes:
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 - Steuerungsaufgaben, die erforderlich sind, um die vergleichenden Leistungsmerkmale des Computersystems auf verschiedenen Plattformen zu bestimmen. Kann verwendet werden, um Optimierungsentscheidungen zu treffen und verschiedene Java-Implementierungen zu vergleichen.