Warum ist diese C ++ Codeausführung im Vergleich zu Java so langsam?

8

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:

  1. g++ -o ... Java 5,5 mal schneller
  2. g++ -O3 -o ... Java 2,9-mal schneller
  3. 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):

  1. Java 1,8 mal schneller
  2. C ++ 1,9-mal schneller
  3. C ++ 2 mal schneller
%Vor%

Java-Code:

%Vor%

C ++ - Code:

%Vor%     
Bill B 03.06.2017, 10:26
quelle

2 Antworten

5

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.

    
Dunes 04.06.2017 10:53
quelle
4

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):

%Vor%

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).

    
rustyx 05.06.2017 09:45
quelle

Tags und Links