5

Ich habe einige Leistungstests bezüglich der Objektzuordnung durchgeführt, als ich auf ein komisches Ergebnis stieß. Ich habe den folgenden Java-Code:Java: Was verursacht die Leistungssteigerung beim wiederholten Aufruf einer Funktion?

public static long TestMethod(){ 
    int len = 10000000; 
    Object[] obs = new Object[len]; 
    long t = System.nanoTime(); 
    for (int i = 0; i < len; i++) { 
     obs[i] = new Object(); 
    } 
    return System.nanoTime() - t; 
} 

public static void main(String... args) throws InterruptedException { 
    for(int i = 0; i < 10; i++){ 
     System.gc(); 
     System.gc();//Wait for the gc to be finished 
     Thread.sleep(1000); 
     System.out.println(TestMethod()); 
    } 
} 

Erwartung: Erster Aufruf langsamer sein wird, dann zweiten Anruf aufgrund eines größeren Speicherraum von dem Betriebssystem und Hotspot-Erweiterungen anfordert. Aber der zweite und der dritte werden fast gleich sein.

Beobachtete Ergebnis:

11284734000 
799837000 
682304000 
304736000 
380770000 
392786000 
374279000 
381611000 
379174000 
407256000 

noch eine beträchtliche Beschleunigung zwischen der dritten und vierten Iteration. Was verursacht diese Beschleunigung? Wie kann ich beim Testen anderer Funktionen sicher sein, dass meine Messungen genau sind? Muss ich die Funktion mehr als vier Mal aufrufen, bevor ich Messungen durchführe?

+0

[Just-in-Time-Compiler Startverzögerung] (http://en.wikipedia.org/wiki/Just-in-time_compilation#Startup_delay_and_optimizations) –

+1

interessant zu lesen: http://stackoverflow.com/questions/504103/how-do-ich-schreibe-a-correct-micro-benchmark-in-java - Insbesondere das Ausführen mit den VM-Parametern "-XX: + PrintCompilation -verbose: gc" hilft dir zu verstehen, was vor sich geht. – assylias

+0

@assylias: Interessant. Kannte diese Parameter nicht. Es scheint, die Funktion in dem zweiten Aufruf neu zu kompilieren und 2 Garbage Collections in den ersten drei Aufrufen und den letzten Aufrufen auszuführen, die keine Speicherbereinigung in den Funktionsaufrufen vornehmen. – user23127

Antwort

4

Was verursacht diese Beschleunigung?

Es ist wahrscheinlich JIT-Kompilierung, aber es könnte auch Beiträge von Code laden und/oder Heap-Aufwärm-Effekte sein.

Wie kann ich sicher sein, dass meine Messungen genau sind, wenn andere Funktionen getestet werden, muss ich die Funktion mehr als vier Mal aufrufen, bevor ich die Messung durchführe?

Sie müssen so etwas tun. Es gibt keine andere Möglichkeit, JVM-Aufwärmeffekte aus Ihren Messungen zu eliminieren und dennoch repräsentative Ergebnisse zu erzielen. Es ist schwierig, gültige "Mikrobenchmarks" für Java zu schreiben, und Sie müssen sich alle Probleme genauer ansehen, bevor Sie es versuchen. Beginnen Sie mit diesem: How do I write a correct micro-benchmark in Java?


Ich würde auch zur Kenntnis, ein paar andere Dinge:

  1. Ihre Versuche, die Kosten für die Garbage-Collection aus Ihren Messungen zu entfernen (Ich gehe davon aus, dass das, was Sie zu sein scheinen gehen) erscheinen fehlgeschlagen zu sein. Es sieht so aus, als ob Sie während der Ausführung von testMethod kleinere Sammlungen erhalten.Dies würde die ~ 7% Variabilität in Ihren "steady state" Ergebnissen erklären.

  2. Die Kosten für die Zuweisung eines Objekts von den Kosten für die Freigabe zu trennen, führt wahrscheinlich zu irreführenden Ergebnissen. Die "Gesamt" -Kosten des Zuweisens eines Objekts umfassen die Kosten des Nullsetzens des Speichers, wenn es recycelt wird ... und dies wird durch den Garbage Collector durchgeführt.

  3. In der Tat sind die amortisierten Kosten pro Objekt eines Allokations-/Sammelzyklus am nützlichsten. Und das hängt (überraschend) von der Menge an Nicht-Müll ab, wenn der GC läuft ... was Ihre Benchmark nicht berücksichtigt.

2

Es gibt mehrere Faktoren, die die Ausführung der Funktion beschleunigen könnte, wie Sie sagten:

  • vor allem JIT an verschiedenen deegrees oder wärend der Anwendung auftreten können (aus diesem Grund Profilierung ohne die Angabe JVM genügend Zeit zum Aufwärmen kann zu falschen Ergebnissen führen)
  • die Anforderung des Speichers an das OS
  • die Wiederverwendung von Speicher auf Heap für Objekte, die Sie Zuteilen

Sie können nicht sicher sein, wann jeder Schritt ausgeführt wird, und Sie können nicht wissen, wann Code in nativen Code kompiliert wird, aber vorausgesetzt, dass dies geschieht, bevor der erste Aufruf nicht korrekt ist, kann es genau dann passieren, wenn die Zeit dazwischen liegt dritte und vierte Iteration oder was auch immer. Leider sind diese Details in der JVM-Implementierung verborgen.