Ich verwende Windows 8.1 x64 mit Java 7 Update 45 x64 (kein 32-Bit-Java installiert) auf einem Surface Pro 2-Tablet.
Der folgende Code benötigt 1688 ms, wenn der Typ von i lang ist, und 109 ms, wenn i ein int ist. Warum ist long (ein 64-Bit-Typ) auf einer 64-Bit-Plattform mit einer 64-Bit-JVM um eine Größenordnung langsamer als int?
Meine einzige Spekulation ist, dass die CPU länger braucht, um eine 64-Bit-Ganzzahl als eine 32-Bit-Ganzzahl hinzuzufügen, aber das scheint unwahrscheinlich. Ich vermute, Haswell verwendet keine Ripple-Carry-Addierer.
Ich führe dies übrigens in Eclipse Kepler SR1 aus.
public class Main {
private static long i = Integer.MAX_VALUE;
public static void main(String[] args) {
System.out.println("Starting the loop");
long startTime = System.currentTimeMillis();
while(!decrementAndCheck()){
}
long endTime = System.currentTimeMillis();
System.out.println("Finished the loop in " + (endTime - startTime) + "ms");
}
private static boolean decrementAndCheck() {
return --i < 0;
}
}
Bearbeiten: Hier sind die Ergebnisse von äquivalentem C ++ - Code, der von VS 2013 (unten), demselben System, kompiliert wurde. lang: 72265 ms int: 74656 ms Diese Ergebnisse befanden sich im 32-Bit-Debug-Modus.
Im 64-Bit-Freigabemodus: lang: 875 ms lang lang: 906 ms int: 1047 ms
Dies deutet darauf hin, dass das Ergebnis, das ich beobachtet habe, eher eine Verrücktheit der JVM-Optimierung als CPU-Einschränkungen ist.
#include "stdafx.h"
#include "iostream"
#include "windows.h"
#include "limits.h"
long long i = INT_MAX;
using namespace std;
boolean decrementAndCheck() {
return --i < 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
cout << "Starting the loop" << endl;
unsigned long startTime = GetTickCount64();
while (!decrementAndCheck()){
}
unsigned long endTime = GetTickCount64();
cout << "Finished the loop in " << (endTime - startTime) << "ms" << endl;
}
Bearbeiten: Ich habe es gerade noch einmal in Java 8 RTM versucht, keine wesentliche Änderung.
quelle
currentTimeMillis()
Ausführen, Ausführen von Code, der trivial vollständig optimiert werden kann usw. stinkt nach unzuverlässigen Ergebnissen.long
als Schleifenzähler verwenden, da der JIT-Compiler die Schleife optimiert hat, als ich eine verwendet habeint
. Man müsste sich die Demontage des generierten Maschinencodes ansehen.Antworten:
Meine JVM macht diese ziemlich einfache Sache mit der inneren Schleife, wenn Sie
long
s verwenden:Es betrügt schwer, wenn Sie
int
s verwenden; Zuerst gibt es einige Irrtümer, die ich nicht zu verstehen behaupte, die aber wie ein Setup für eine abgewickelte Schleife aussehen:dann die abgewickelte Schleife selbst:
dann der Teardown-Code für die abgewickelte Schleife, selbst ein Test und eine gerade Schleife:
Für Ints geht es also 16-mal schneller, weil die JIT die
int
Schleife 16-mal abgewickelt hat, die Schleife aber überhaupt nicht abgewickelt hatlong
.Der Vollständigkeit halber hier der Code, den ich tatsächlich ausprobiert habe:
Die Assembly-Dumps wurden mit den Optionen generiert
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
. Beachten Sie, dass Sie mit Ihrer JVM-Installation herumspielen müssen, damit dies auch für Sie funktioniert. Sie müssen eine zufällige gemeinsam genutzte Bibliothek genau an der richtigen Stelle platzieren, da dies sonst fehlschlägt.quelle
long
Version langsamer ist, sondern dass die Versionint
schneller ist. Das macht Sinn. Wahrscheinlich wurde nicht so viel Aufwand in die Optimierung derlong
Ausdrücke durch die JIT investiert .gcc
verwendet-f
als Befehlszeilenschalter für "flag", und dieunroll-loops
Optimierung wird aktiviert , indem gesagt wird-funroll-loops
. Ich benutze nur "Abrollen", um die Optimierung zu beschreiben.i-=16
, was natürlich 16x schneller ist.Der JVM-Stapel wird in Form von Wörtern definiert , deren Größe ein Implementierungsdetail ist, die jedoch mindestens 32 Bit breit sein muss. Der JVM-Implementierer verwendet möglicherweise 64-Bit-Wörter, aber der Bytecode kann sich nicht darauf verlassen. Daher müssen Operationen mit
long
oderdouble
Werte mit besonderer Sorgfalt behandelt werden. Insbesondere sind die JVM-Integer-Verzweigungsbefehle genau für den Typ definiertint
.Bei Ihrem Code ist die Demontage aufschlussreich. Hier ist der Bytecode für die
int
vom Oracle JDK 7 kompilierte Version:Beachten Sie, dass die JVM den Wert Ihrer statischen Aufladung lädt
i
(0) , einen (3-4) subtrahiert, den Wert auf dem Stapel (5) dupliziert und ihn zurück in die Variable (6) schiebt. Es führt dann einen Vergleich mit Null durch und gibt zurück.Die Version mit dem
long
ist etwas komplizierter:Wenn die JVM den neuen Wert auf dem Stapel (5) dupliziert, muss sie zunächst zwei Stapelwörter duplizieren. In Ihrem Fall ist es durchaus möglich, dass dies nicht teurer ist als das Duplizieren eines Wortes, da die JVM bei Bedarf ein 64-Bit-Wort verwenden kann. Sie werden jedoch feststellen, dass die Verzweigungslogik hier länger ist. Die JVM keinen Befehl haben zu einem Vergleich
long
mit Null, so dass es einem konstanten drücken hat0L
auf den Stapel (9), habe einen allgemeinenlong
Vergleich (10), und dann Zweig auf dem Wert , dass Berechnung .Hier sind zwei plausible Szenarien:
long
Version mehr Arbeit geleistet , indem mehrere zusätzliche Werte verschoben und gepoppt werden. Diese befinden sich auf dem virtuell verwalteten Stapel und nicht auf dem realen hardwareunterstützten CPU-Stapel. Wenn dies der Fall ist, werden Sie nach dem Aufwärmen immer noch einen signifikanten Leistungsunterschied feststellen.Ich empfehle Sie , einen korrekten - Micro schreiben die Wirkung des mit dem JIT - Kick in, zu beseitigen und dies auch mit einem Endzustand versuchen , die nicht Null ist, um die JVM zu zwingen , denselben Vergleich zu folgendem Thema zu tun ,
int
dass es mit der tutlong
.quelle
== 0
, was ein unverhältnismäßig großer Teil der Benchmark-Ergebnisse zu sein scheint. Es scheint mir wahrscheinlicher, dass OP versucht, einen allgemeineren Bereich von Operationen zu messen, und diese Antwort weist darauf hin, dass die Benchmark stark auf nur eine dieser Operationen ausgerichtet ist.Die grundlegende Dateneinheit in einer Java Virtual Machine ist das Wort. Die Auswahl der richtigen Wortgröße bleibt bei der Implementierung der JVM. Eine JVM-Implementierung sollte eine Mindestwortgröße von 32 Bit wählen. Es kann eine höhere Wortgröße wählen, um die Effizienz zu steigern. Es gibt auch keine Einschränkung, dass eine 64-Bit-JVM nur 64-Bit-Wörter auswählen sollte.
Die zugrunde liegende Architektur regelt nicht, dass die Wortgröße auch gleich sein sollte. JVM liest / schreibt Daten Wort für Wort. Dies ist der Grund , warum es länger ein vielleicht nimmt lange als ein int .
Hier finden Sie weitere Informationen zum gleichen Thema.
quelle
Ich habe gerade einen Benchmark mit Bremssattel geschrieben .
Die Ergebnisse stimmen ziemlich gut mit dem ursprünglichen Code überein: eine ~ 12-fache Beschleunigung für die Verwendung von
int
overlong
. Es scheint sicher, dass die von tmyklebu oder etwas sehr Ähnlichem gemeldete Schleifenabwicklung stattfindet .Das ist mein Code; Beachten Sie, dass es einen frisch erstellten Snapshot von verwendet
caliper
, da ich nicht herausfinden konnte, wie man gegen die vorhandene Beta-Version codiert.quelle
Für die Aufzeichnung macht diese Version ein grobes "Aufwärmen":
Die Gesamtzeiten verbessern sich um etwa 30%, aber das Verhältnis zwischen beiden bleibt ungefähr gleich.
quelle
int
ist 20-mal schneller) mit diesem Code.Für die Aufzeichnungen:
wenn ich benutze
(geändert "l--" in "l = l - 1l") Die Langzeitleistung verbessert sich um ~ 50%
quelle
Ich habe keine 64-Bit-Maschine zum Testen, aber der ziemlich große Unterschied deutet darauf hin, dass mehr als der etwas längere Bytecode am Werk ist.
Ich sehe sehr nahe Zeiten für long / int (4400 vs 4800ms) auf meinem 32-Bit 1.7.0_45.
Dies ist nur eine Vermutung , aber ich vermute stark , dass dies die Auswirkung einer Strafe für Speicherfehlausrichtung ist. Versuchen Sie, einen öffentlichen statischen int-Dummy = 0 hinzuzufügen, um den Verdacht zu bestätigen / abzulehnen. Vor der Erklärung von i. Dadurch wird i im Speicherlayout um 4 Byte nach unten gedrückt und möglicherweise für eine bessere Leistung richtig ausgerichtet.Es wurde bestätigt, dass das Problem nicht verursacht wird.BEARBEITEN:
Der Grund dafür ist, dass die VM Felder möglicherweise nicht neu anordnet anordnet und für eine optimale Ausrichtung eine Auffüllung hinzufügt, da dies die JNI beeinträchtigen kann(Nicht der Fall).quelle