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
ich nur von CPU-Cache-Misses denken kann, die in der nicht geschieht JVM selbst. –
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
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). –