Ich habe kürzlich einen rechenintensiven Algorithmus in Java geschrieben und ihn dann nach C ++ übersetzt. Zu meiner Überraschung lief das C ++ wesentlich langsamer. Ich habe jetzt ein viel kürzeres Java-Testprogramm und ein entsprechendes C ++ - Programm geschrieben - siehe unten. Mein ursprünglicher Code enthielt, wie auch der Testcode, viel Array-Zugriff. Die Ausführung von C ++ dauert 5,5 Mal länger (siehe Kommentar am Ende jedes Programms).
Schlussfolgerungen nach 1 st 21 Kommentare unten ...
Testcode:
g++ -o ...
Java 5,5 mal schneller g++ -O3 -o ...
Java 2,9-mal schneller g++ -fprofile-generate -march=native -O3 -o ...
(run, dann g++ -fprofile-use
etc) Java 1.07-mal schneller. Mein ursprüngliches Projekt (viel komplexer als Testcode):
Java-Code:
%Vor%C ++ - Code:
%Vor%Das einzige Mal, dass ich das C ++ - Programm dazu bringen konnte, Java zu übertreffen, war die Verwendung von Profiling-Informationen. Dies zeigt, dass in den Laufzeitinformationen (die Java standardmäßig erhält) etwas enthalten ist, das eine schnellere Ausführung ermöglicht.
Abgesehen von einer nicht-trivialen if-Anweisung ist in Ihrem Programm nicht viel los. Das heißt, ohne das gesamte Programm zu analysieren, ist es schwierig vorherzusagen, welcher Zweig am wahrscheinlichsten ist. Dies führt mich zu der Annahme, dass es sich um eine Fehleinschätzung der Branche handelt. Moderne CPUs tun Befehlspipeline , was einen höheren CPU-Durchsatz ermöglicht. Dies erfordert jedoch eine Vorhersage, was die nächsten auszuführenden Befehle sind. Wenn die Schätzung falsch ist, muss die Befehlspipeline gelöscht werden und die richtigen Anweisungen geladen werden (was Zeit benötigt).
Zur Kompilierungszeit hat der Compiler nicht genügend Informationen, um vorherzusagen, welcher Zweig am wahrscheinlichsten ist. CPUs tun ein bisschen Verzweigungsprognose ebenso, aber das ist im Allgemeinen entlang der Linien von Schleifenschleife und wenn if (anstatt sonst).
Java hat jedoch den Vorteil, Informationen sowohl zur Laufzeit als auch zur Kompilierzeit nutzen zu können. Dies ermöglicht Java, den mittleren Zweig als den am häufigsten auftretenden Zweig zu identifizieren, so dass dieser Zweig für die Pipeline vorhergesagt wird.
Irgendwie können GCC und call diese Schleife nicht abrollen und die Invarianten sogar in -O3 und -Os herausziehen, aber Java tut es.
Der fertige JIT-Assembler-Code von Java ist diesem ähnlich (in Wirklichkeit zweimal wiederholt ):
%Vor%Mit dieser Schleife bekomme ich exakt die gleichen Zeiten (6.4s) zwischen C ++ und Java.
Warum ist das legal zu tun? Weil ARRAY_LENGTH
100 ist, was ein Vielfaches von 4 ist. So kann i
nur 100 überschreiten und alle 4 Iterationen auf 0 zurückgesetzt werden.
Dies scheint eine Gelegenheit für Verbesserungen bei GCC und Clang zu sein; Sie können Schleifen nicht ausrollen, für die die Gesamtzahl der Iterationen unbekannt ist. Aber selbst wenn das Ausrollen erzwungen wird, erkennen sie Teile der Schleife nicht, die nur für bestimmte Iterationen gelten.
Zu Ihren Ergebnissen in einem komplexeren Code (aka reales Leben): Javas Optimizer ist außergewöhnlich gut für kleine Schleifen, es wurde viel darüber nachgedacht, aber Java verliert viel Zeit bei virtuellen Aufrufen und GC.
Am Ende kommt es auf Maschinenanweisungen an, die auf einer konkreten Architektur laufen, wer immer das beste Set herausbringt, gewinnt. Gehen Sie nicht davon aus, dass der Compiler "das Richtige tun wird", schauen Sie und der generierte Code, Profil, wiederholen.
Zum Beispiel, wenn Sie Ihre Schleife nur ein wenig umstrukturieren:
%Vor%Dann übertrifft C ++ Java (5.9s gegenüber 6.4s). ( überarbeitete C ++ Assembly )
Und wenn Sie einen leichten Überlauf zulassen können (mehr intArray
Elemente inkrementieren, nachdem die Ausgangsbedingung erreicht wurde):
Jetzt kann clang die Schleife vektorisieren und erreicht die Geschwindigkeit von 3.5s gegenüber Java 4.8s (GCC ist leider immer noch nicht in der Lage, es zu vektorisieren).
Tags und Links java c++ performance