Ist gcc asm volatile äquivalent zur gfortran-Standardeinstellung für Rekursionen?

8

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:

  1. Warum fügt Assembly Kommentare hinzu verursachen solche radikale Änderung in generierten Code?
  2. Arbeiten mit asm volatile ( ""::: "Erinnerung")
  3. äquivalent zu asm volatile in gfortran

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ändert

Assembly (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 :

  • Warum kann gcc diese "Optimierung" nicht sehen, wenn gfortan eindeutig kann?
  • Die Inline-Assembly-Zeile muss vor der return-Anweisung stehen. Setzen Sie es woanders hin und es wird keine Wirkung haben. Warum?
  • Ist dieser Verhaltenskompiler spezifisch? Zum Beispiel können Sie das gleiche Verhalten mit Clang / MSVC imitieren?
  • Gibt es sicherere Methoden, um Rekursionen in C oder C++ schneller zu machen (ohne auf Inline-Assemblierung oder Iteration-artige Codierung angewiesen zu sein)? Variadische Vorlagen vielleicht?

AKTUALISIEREN :

  • Das oben gezeigte Ergebnis bezieht sich auf 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.
  • Das Problem kann auch repliziert werden (behoben?) Wenn ich 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.
  • Als Michael Karcher erwähnt haben, können wir stattdessen die -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.
  • Ich habe das gleiche Beispiel mit 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%     
romeric 06.10.2015, 16:07
quelle

1 Antwort

4
___ tag123c ___ C ++ ist eine universelle Programmiersprache. Es wurde ursprünglich als Erweiterung von C entworfen und behält eine ähnliche Syntax, ist aber jetzt eine komplett andere Sprache. Verwenden Sie dieses Tag für Fragen zu Code, der mit einem C ++ - Compiler kompiliert werden soll. ___ tag123optimierung ___ Optimierung ist der Akt der Verbesserung einer Methode oder eines Designs. In der Programmierung nimmt die Optimierung normalerweise die Form an, die Geschwindigkeit eines Algorithmus zu erhöhen oder die benötigten Ressourcen zu reduzieren. Eine weitere Bedeutung der Optimierung sind numerische Optimierungsalgorithmen. ___ qstnhdr ___ Ist gcc asm volatile äquivalent zur gfortran-Standardeinstellung für Rekursionen? ___ tag123gcc ___ GCC ist die GNU Compiler-Sammlung. Es ist der De-facto-Standard-C-Compiler unter Linux und unterstützt auch viele andere Sprachen und Plattformen. ___ answer3195788 ___

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:

%Vor%

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.

    
___ tag123fortran ___ Fortran ist eine universelle, prozedurale, imperative Programmiersprache, die besonders für numerische Berechnungen und wissenschaftliche Berechnungen geeignet ist. Seit 2003 unterstützt der Standard Fortran auch die objektorientierte Programmierung. Dieses Tag sollte auf alle Fragen zur Fortran-Sprache angewendet werden. Andere spezifische Tags können für Compiler, Sprachrevisionen und bestimmte Aspekte der Verwendung hinzugefügt werden. ___ tag123assembly ___ Assemblersprache (asm) Programmierfragen. Achten Sie darauf, auch mit dem Prozessor und / oder Befehlssatz, die Sie verwenden, sowie den Assembler TAG. WARNUNG: Verwenden Sie für .NET-Assemblies stattdessen das Tag [.net-assembly]. Verwenden Sie für Java ASM stattdessen das Tag [java-bytecode-asm]. ___ qstntxt ___

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:

  1. Warum fügt Assembly Kommentare hinzu verursachen solche radikale Änderung in generierten Code?
  2. Arbeiten mit asm volatile ( ""::: "Erinnerung")
  3. äquivalent zu asm volatile in gfortran

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ändert

Assembly (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 :

  • Warum kann %code% diese "Optimierung" nicht sehen, wenn %code% eindeutig kann?
  • Die Inline-Assembly-Zeile muss vor der return-Anweisung stehen. Setzen Sie es woanders hin und es wird keine Wirkung haben. Warum?
  • Ist dieser Verhaltenskompiler spezifisch? Zum Beispiel können Sie das gleiche Verhalten mit Clang / MSVC imitieren?
  • Gibt es sicherere Methoden, um Rekursionen in %code% oder %code% schneller zu machen (ohne auf Inline-Assemblierung oder Iteration-artige Codierung angewiesen zu sein)? Variadische Vorlagen vielleicht?

AKTUALISIEREN :

  • Das oben gezeigte Ergebnis bezieht sich auf %code% . Ich habe auch versucht, es mit %code% und %code% zu kompilieren und ich bekomme identische Ergebnisse.
  • Das Problem kann auch repliziert werden (behoben?) Wenn ich %code% nicht deklariere, deklariere ich das Eingabeargument als volatile dh %code% anstelle von %code% , obwohl dies zu einer etwas langsameren Laufzeit führt meine Maschine.
  • Als Michael Karcher erwähnt haben, können wir stattdessen die %code% -Flag übergeben, um dieses Problem zu beheben. Da dieses Flag auf %code% level und darüber hinaus aktiviert ist, behebt sogar das Kompilieren mit %code% dieses Problem.
  • Ich habe das gleiche Beispiel mit %code% mit %code% ausgeführt und obwohl die Situation nicht identisch ist, scheint %code% auch schneller Code mit %code% zu erzeugen.

Clang Timing:

%Vor%     
___
Michael Karcher 18.10.2015, 08:23
quelle