Ich habe gerade mit rekursiven Funktionen in C++
und Fortran
herumgespielt und festgestellt, dass eine einfache rekursive Funktion in Fortran
fast doppelt so schnell ist wie die entsprechende C++
-Funktion. Nun, bevor ich darauf eingehe, weiß ich, dass es ähnliche Fragen gibt, speziell:
Allerdings bin ich etwas genauer und rätselhafter, als der Fortran-Compiler zu tun scheint, was Sie mit asm volatile
in gcc
erreichen können. Um Ihnen einen Kontext zu geben, betrachten wir die folgende rekursive Fibonacci number
Implementierung:
Fortran-Code:
%Vor%Kompiliert mit:
%Vor%C ++ - Code:
%Vor%Kompiliert mit:
%Vor%Zeit:
%Vor% So gfortran
ist dort doppelt so schnell verglichen mit gcc
. Wenn ich auf den Assembler-Code schaue, bekomme ich
Versammlung (Fortran):
%Vor%Assembly (C ++):
%Vor% Beide Assemblercodes bestehen aus fast gleichen Blöcken / Labels, die immer wieder wiederholt werden. Wie Sie sehen können, ruft die Fortran Assembly zwei Aufrufe von fib
function auf, während in der C ++ Assembly gcc
wahrscheinlich alle rekursiven Aufrufe entrollt hat, was wahrscheinlich mehr Stack push/pop
und Tail Jumps erfordert.
Nun, wenn ich nur einen Inline-Assembly-Kommentar in den C ++ - Code wie folgt einfügen möchte
Modifizierter C ++ Code:
%Vor%Der generierte Assemblercode wird in
geändertAssembly (C ++ geändert):
%Vor% Sie können jetzt zwei Aufrufe von fib
function sehen. Timing gibt mir
Zeit:
%Vor% Ich kenne den Effekt von asm
ohne Ausgabe und asm volatile
ist, aggressive Compiler-Optimierungen zu unterdrücken, aber in diesem Fall dachte gcc
, dass es zu schlau war, aber am Ende einen weniger effizienten Code erzeugte.
Die Frage ist also :
gcc
diese "Optimierung" nicht sehen, wenn gfortan
eindeutig kann? C
oder C++
schneller zu machen (ohne auf Inline-Assemblierung oder Iteration-artige Codierung angewiesen zu sein)? Variadische Vorlagen vielleicht? AKTUALISIEREN :
gcc 4.8.4
. Ich habe auch versucht, es mit gcc 4.9.2
und gcc 5.2
zu kompilieren und ich bekomme identische Ergebnisse. asm
nicht deklariere, deklariere ich das Eingabeargument als volatile dh (volatile int n)
anstelle von (const int n)
, obwohl dies zu einer etwas langsameren Laufzeit führt meine Maschine. -fno-optimize-sibling-calls
-Flag übergeben, um dieses Problem zu beheben. Da dieses Flag auf -O2
level und darüber hinaus aktiviert ist, behebt sogar das Kompilieren mit -O1
dieses Problem. clang 3.5.1
mit -O3 -march=native
ausgeführt und obwohl die Situation nicht identisch ist, scheint clang
auch schneller Code mit asm
zu erzeugen. Clang Timing:
%Vor%Siehe das fett gedruckte Ende dieser Antwort, um ein schnelles Programm zu erhalten, das von gcc generiert wurde. Lesen Sie die Antwort auf die Antworten auf die vier Fragen.
Ihre erste Frage geht davon aus, dass gfortran
eine Optimierungsmöglichkeit sehen kann, die gcc
nicht sehen konnte. In der Tat ist das Gegenteil der Fall. gcc
hat festgestellt, dass es sich um eine Optimierungsmöglichkeit handelt, während gfortran
es verpasst hat. Ach, gcc
war falsch und die angewendete Optimierung stellt sich als 100% Geschwindigkeitsverlust auf Ihrem System heraus (vergleichbar mit mir).
Um Ihre zweite Frage zu beantworten: Die asm
-Anweisung hat eine interne Transformation verhindert, die gcc
zur falschen Optimierungsmöglichkeit gemacht hat. Ohne die asm
-Anweisung wurde Ihr Code (gültig) in folgendes umgewandelt:
Die return-Anweisung, die den rekursiven Aufruf enthält, löst die "Geschwisteranrufoptimierung" aus, die Ihren Code pessimisiert. Das Einschließen der Anweisung asm verhindert, dass die Rückgabeanweisung über sie hinweg verschoben wird.
Momentan habe ich nur gcc zur Hand, also kann ich das Verhalten anderer Compiler nicht versuchen, Ihre dritte Frage durch Beweise zu beantworten, aber das scheint definitiv kompilerabhängig zu sein. Sie sind in eine Eigenart (oder einen Fehler, wie auch immer Sie es nennen) von gcc geraten, dass es beim Versuch, es zu optimieren, schlechten Code erzeugt. Die Optimierer verschiedener Compiler unterscheiden sich sehr stark. Daher ist es sehr wahrscheinlich, dass ein anderer Compiler Ihren Code nicht wie bei gcc
falsch optimiert. Auf der anderen Seite sind Code-Transformationen für die Optimierung ein gut erforschtes Thema, und die meisten Compiler implementieren ähnliche Ansätze zur Optimierung, so dass es möglich ist, dass ein anderer Compiler in dieselbe Falle wie gcc
tritt.
Um Ihre letzte Frage zu beantworten: Es ist kein Problem mit C / C ++ im Vergleich zu Fortan, aber ein Problem mit gcc
, das dieses Beispielprogramm (und wahrscheinlich ähnliche Produktionsprogramme) durcheinander bringt. Es gibt also keine Möglichkeit, die Rekursion in C++
schneller zu machen, aber es gibt eine Möglichkeit, das Beispiel in gcc
zu beschleunigen, indem die problematische Optimierung deaktiviert wird: < strong> -fno-optimize-sibling-calls
, was (auf meinem System in einem einzigen Testlauf) zu noch schnellerem Code führt als nur das Einfügen der asm
-Anweisung.
Ich habe gerade mit rekursiven Funktionen in %code% und %code% herumgespielt und festgestellt, dass eine einfache rekursive Funktion in %code% fast doppelt so schnell ist wie die entsprechende %code% -Funktion. Nun, bevor ich darauf eingehe, weiß ich, dass es ähnliche Fragen gibt, speziell:
Allerdings bin ich etwas genauer und rätselhafter, als der Fortran-Compiler zu tun scheint, was Sie mit %code% in %code% erreichen können. Um Ihnen einen Kontext zu geben, betrachten wir die folgende rekursive %code% Implementierung:
Fortran-Code:
%Vor%Kompiliert mit:
%Vor%C ++ - Code:
%Vor%Kompiliert mit:
%Vor%Zeit:
%Vor%So %code% ist dort doppelt so schnell verglichen mit %code% . Wenn ich auf den Assembler-Code schaue, bekomme ich
Versammlung (Fortran):
%Vor%Assembly (C ++):
%Vor%Beide Assemblercodes bestehen aus fast gleichen Blöcken / Labels, die immer wieder wiederholt werden. Wie Sie sehen können, ruft die Fortran Assembly zwei Aufrufe von %code% function auf, während in der C ++ Assembly %code% wahrscheinlich alle rekursiven Aufrufe entrollt hat, was wahrscheinlich mehr Stack %code% und Tail Jumps erfordert.
Nun, wenn ich nur einen Inline-Assembly-Kommentar in den C ++ - Code wie folgt einfügen möchte
Modifizierter C ++ Code:
%Vor%Der generierte Assemblercode wird in
geändertAssembly (C ++ geändert):
%Vor%Sie können jetzt zwei Aufrufe von %code% function sehen. Timing gibt mir
Zeit:
%Vor%Ich kenne den Effekt von %code% ohne Ausgabe und %code% ist, aggressive Compiler-Optimierungen zu unterdrücken, aber in diesem Fall dachte %code% , dass es zu schlau war, aber am Ende einen weniger effizienten Code erzeugte.
Die Frage ist also :
AKTUALISIEREN :
Clang Timing:
%Vor%Tags und Links optimization c++ gcc assembly fortran