2017-04-07 1 views
2

Der folgende Funktionscode isoliert das Problem. Grundsätzlich bin ich Timing der Methode addToStorage. Ich beginne damit, es eine Million Mal auszuführen, und ich kann seine Zeit auf etwa 723 Nanosekunden reduzieren. Dann mache ich eine kurze Pause (mit einer Busy-Spinning-Methode, um den CPU-Kern nicht freizugeben) und Zeit die Methode wieder N-mal, an einem anderen Speicherort Code. Für meine Überraschung finde ich, dass je kleiner das N ist desto größer ist die addToStorage Latenz.Was passiert in der JVM, so dass ein Methodenaufruf in Java langsamer wird, wenn Sie ihn irgendwo anders in Ihrem Code aufrufen?

Zum Beispiel:

If N = 1 then I get 3.6 micros 
If N = 2 then I get 3.1 and 2.5 micros 
if N = 5 then I get 3.7, 1.8, 1.7, 1.5 and 1.5 micros 

Weiß jemand, warum dies geschieht und wie man es beheben? Ich möchte, dass meine Methode immer so schnell wie möglich funktioniert, egal wo ich sie nenne.

Hinweis: Ich würde nicht denken, dass es Thread verwandt ist, da ich Thread.sleep nicht verwende. Ich habe auch getestet taskset, um meinen Thread zu einem CPU-Kern mit den gleichen Ergebnissen zu setzen.

import java.util.ArrayList; 
import java.util.List; 

public class JvmOdd { 

    private final StringBuilder sBuilder = new StringBuilder(1024); 
    private final List<String> storage = new ArrayList<String>(1024 * 1024); 

    public void addToStorage() { 

     sBuilder.setLength(0); 

     sBuilder.append("Blah1: ").append(System.nanoTime()).append('\n'); 
     sBuilder.append("Blah2: ").append(System.nanoTime()).append('\n'); 
     sBuilder.append("Blah3: ").append(System.nanoTime()).append('\n'); 
     sBuilder.append("Blah4: ").append(System.nanoTime()).append('\n'); 
     sBuilder.append("Blah5: ").append(System.nanoTime()).append('\n'); 
     sBuilder.append("Blah6: ").append(System.nanoTime()).append('\n'); 
     sBuilder.append("Blah7: ").append(System.nanoTime()).append('\n'); 
     sBuilder.append("Blah8: ").append(System.nanoTime()).append('\n'); 
     sBuilder.append("Blah9: ").append(System.nanoTime()).append('\n'); 
     sBuilder.append("Blah10: ").append(System.nanoTime()).append('\n'); 

     storage.add(sBuilder.toString()); 
    } 

    public static long mySleep(long t) { 
     long x = 0; 
     for(int i = 0; i < t * 10000; i++) { 
      x += System.currentTimeMillis()/System.nanoTime(); 
     } 
     return x; 
    } 

    public static void main(String[] args) throws Exception { 

     int warmup = Integer.parseInt(args[0]); 
     int mod = Integer.parseInt(args[1]); 
     int passes = Integer.parseInt(args[2]); 
     int sleep = Integer.parseInt(args[3]); 

     JvmOdd jo = new JvmOdd(); 

     // first warm up 

     for(int i = 0; i < warmup; i++) { 
      long time = System.nanoTime(); 
      jo.addToStorage(); 
      time = System.nanoTime() - time; 
      if (i % mod == 0) System.out.println(time); 
     } 

     // now see how fast the method is: 

     while(true) { 

      System.out.println(); 

      // Thread.sleep(sleep); 
      mySleep(sleep); 

      long minTime = Long.MAX_VALUE; 

      for(int i = 0; i < passes; i++) { 
       long time = System.nanoTime(); 
       jo.addToStorage(); 
       time = System.nanoTime() - time; 
       if (i > 0) System.out.print(','); 
       System.out.print(time); 
       minTime = Math.min(time, minTime); 
      } 
      System.out.println("\nMinTime: " + minTime); 
     } 
    } 
} 

Executing:

$ java -server -cp . JvmOdd 1000000 100000 1 5000 
59103 
820 
727 
772 
734 
767 
730 
726 
840 
736 

3404 
MinTime: 3404 
+0

ich nur von CPU-Cache-Misses denken kann, die in der nicht geschieht JVM selbst. –

+0

Guter Punkt @JornVernee! Ist der Unterschied nicht zu groß für einen CPU-Cache-Fehler? Auch im zweiten oder dritten Aufruf sollte die CPU zwischengespeichert haben, was sie benötigt oder nicht? Wie würden wir diese Hypothese weiter untersuchen? – LatencyFighter

+0

Der Unterschied scheint nicht groß, Speicherabrufe können bis zu 1000 ALU-Zyklen dauern. Das hängt wirklich davon ab, auf welcher CPU Sie laufen, ich kenne keine Methode, um es zu testen (vielleicht, den CPU-Cache vor dem Fortsetzen zu entladen, wenn das möglich ist, ist das Ihre CPU, nicht Java). –

Antwort

0

Java intern verwendet JIT (Just in Compiler). Basierend auf der Häufigkeit, mit der dieselbe Methode ausgeführt wird, optimiert sie den Befehl und führt eine bessere Leistung aus. Für kleinere Werte wäre die Verwendung der Methode normal, die nicht unter die Optimierung fällt, die die Ausführungszeit mehr anzeigt. Wenn die gleiche Methode mehr Zeit aufgerufen hat, verwendet sie JIT und wird aufgrund der optimierten Anweisung für dieselbe Methodenausführung in kürzerer Zeit ausgeführt.

5

Hier ist so viel los, dass ich nicht weiß, wo ich anfangen soll. Aber läßt hier beginnen ....

  long time = System.nanoTime(); 
      jo.addToStorage(); 
      time = System.nanoTime() - time; 

Die Latenz von addToStoarge() kann diese Technik nicht gemessen werden. Es läuft einfach zu schnell, was bedeutet, dass Sie wahrscheinlich unter der Auflösung der Uhr liegen. Ohne dies zu tun, meine Vermutung ist, dass Ihre Messungen von Clock-Edge-Counts dominiert werden. Sie müssen die Arbeitseinheit auffüllen, um eine Messung mit geringeren Rauschpegeln zu erhalten.

Was passiert? Es gibt eine Reihe von Call-Site-Optimierungen, von denen das wichtigste Inlining ist. Inlining würde die Call-Site vollständig eliminieren, aber es ist eine pfadspezifische Optimierung. Wenn Sie die Methode von einem anderen Ort aus aufrufen, würde dies dem langsamen Pfad folgen, bei dem eine virtuelle Methodensuche gefolgt von einem Sprung zu diesem Code ausgeführt wird. Um also die Vorteile des Inlining von einem anderen Weg aus zu sehen, müsste dieser Pfad auch "aufgewärmt" werden.

Ich würde dringend empfehlen, dass Sie beide JMH betrachten (im Lieferumfang des JDK enthalten). Es gibt Einrichtungen wie Blackhole, die mit den Auswirkungen der CPU-Uhren herunterfahren hilft. Vielleicht möchten Sie auch die Qualität der Bank mit Hilfe von Tools wie JITWatch (Adopt OpenJDK-Projekt) bewerten, die Logs von der JIT produziert und Ihnen helfen, sie zu unterbrechen.

+0

Übrigens gibt es eine großartige Diskussion über die [mechanische Sympathie Google-Gruppe] (https://groups.google.com/d/msg/mechanical-sympathy/E7keRLsVcyk/9pGXTySrDQAJ) über genau diese Frage. – Brice

+1

Eine gute Diskussion in der Tat .... Ich hielt die irrige Idee, dass die mySleep() -Methode nicht weggeschmissen würde. In der Vergangenheit konnte ich die Eliminierung von toten Codes verhindern, indem ich void durch einen Rückgabewert ersetzte. Sobald mySleep() jedoch inline ist, wird die Wertabhängigkeit verschwinden, nach der die inline'd mySleep() DCE-Operation ausgeführt werden kann. – Kirk

Verwandte Themen