Kürzlich habe ich festgestellt, dass das Deklarieren eines Arrays mit 64 Elementen viel schneller (> 1000-fach) ist als das Deklarieren des gleichen Array-Typs mit 65 Elementen.
Hier ist der Code, mit dem ich das getestet habe:
public class Tests{
public static void main(String args[]){
double start = System.nanoTime();
int job = 100000000;//100 million
for(int i = 0; i < job; i++){
double[] test = new double[64];
}
double end = System.nanoTime();
System.out.println("Total runtime = " + (end-start)/1000000 + " ms");
}
}
Dies läuft in etwa 6 ms, wenn ich ersetzen new double[64]
mit new double[65]
ihm ca. 7 Sekunden dauert. Dieses Problem wird exponentiell schwerwiegender, wenn der Job auf immer mehr Threads verteilt wird, von denen mein Problem stammt.
Dieses Problem tritt auch bei verschiedenen Arten von Arrays wie int[65]
oder auf String[65]
. Dieses Problem tritt bei großen Zeichenfolgen nicht auf: String test = "many characters";
tritt jedoch auf, wenn dies in geändert wirdString test = i + "";
Ich habe mich gefragt, warum dies der Fall ist und ob es möglich ist, dieses Problem zu umgehen.
System.nanoTime()
sollteSystem.currentTimeMillis()
für das Benchmarking vorgezogen werden .byte
anstelle von ausführedouble
.Antworten:
Sie beobachten ein Verhalten, das durch die vom JIT-Compiler Ihrer Java-VM vorgenommenen Optimierungen verursacht wird . Dieses Verhalten ist reproduzierbar und wird mit skalaren Arrays mit bis zu 64 Elementen ausgelöst. Es wird nicht mit Arrays ausgelöst, die größer als 64 sind.
Bevor wir auf Details eingehen, schauen wir uns den Körper der Schleife genauer an:
Der Körper hat keine Wirkung (beobachtbares Verhalten) . Das heißt, es macht außerhalb der Programmausführung keinen Unterschied, ob diese Anweisung ausgeführt wird oder nicht. Gleiches gilt für die gesamte Schleife. Es kann also vorkommen, dass der Code-Optimierer die Schleife in etwas (oder nichts) mit derselben Funktion und unterschiedlichem Timing-Verhalten übersetzt.
Für Benchmarks sollten Sie mindestens die folgenden zwei Richtlinien einhalten. Wenn Sie dies getan hätten, wäre der Unterschied erheblich geringer gewesen.
Gehen wir nun auf Details ein. Es überrascht nicht, dass eine Optimierung für skalare Arrays ausgelöst wird, die nicht größer als 64 Elemente sind. Die Optimierung ist Teil der Escape-Analyse . Es legt kleine Objekte und kleine Arrays auf den Stapel, anstatt sie auf dem Heap zuzuweisen - oder optimiert sie noch besser vollständig. Einige Informationen dazu finden Sie in dem folgenden Artikel von Brian Goetz aus dem Jahr 2005:
Die Optimierung kann mit der Befehlszeilenoption deaktiviert werden
-XX:-DoEscapeAnalysis
. Der magische Wert 64 für skalare Arrays kann auch in der Befehlszeile geändert werden. Wenn Sie Ihr Programm wie folgt ausführen, gibt es keinen Unterschied zwischen Arrays mit 64 und 65 Elementen:Trotzdem rate ich dringend davon ab, solche Befehlszeilenoptionen zu verwenden. Ich bezweifle, dass dies bei einer realistischen Anwendung einen großen Unterschied macht. Ich würde es nur verwenden, wenn ich von der Notwendigkeit absolut überzeugt wäre - und nicht auf den Ergebnissen einiger Pseudo-Benchmarks.
quelle
Es gibt eine Reihe von Möglichkeiten, wie es je nach Größe eines Objekts zu Unterschieden kommen kann.
Wie nosid angegeben hat, kann der JITC (höchstwahrscheinlich) kleine "lokale" Objekte auf dem Stapel zuweisen, und der Größengrenzwert für "kleine" Arrays kann bei 64 Elementen liegen.
Das Zuweisen auf dem Stapel ist erheblich schneller als das Zuweisen auf dem Heap, und genauer gesagt, der Stapel muss nicht durch Müll gesammelt werden, sodass der GC-Overhead erheblich reduziert wird. (Und für diesen Testfall beträgt der GC-Overhead wahrscheinlich 80-90% der gesamten Ausführungszeit.)
Sobald der Wert dem Stapel zugewiesen ist, kann die JITC eine "Eliminierung des toten Codes" durchführen, feststellen, dass das Ergebnis von
new
niemals irgendwo verwendet wird, und, nachdem sichergestellt wurde, dass keine Nebenwirkungen verloren gehen, den gesamtennew
Vorgang eliminieren . und dann die (jetzt leere) Schleife selbst.Selbst wenn die JITC keine Stapelzuweisung durchführt, ist es durchaus möglich, dass Objekte, die kleiner als eine bestimmte Größe sind, in einem Heap anders zugewiesen werden (z. B. aus einem anderen "Raum") als größere Objekte. (Normalerweise würde dies jedoch nicht zu so dramatischen Zeitunterschieden führen.)
quelle