Berechne die Fibonacci-Zahl (rekursive Methode) in der Kompilierzeit (constexpr) in C ++ 11

8
___ answer22645853 ___

Versuchen Sie Folgendes:

%Vor%

Mit clang und -o3 wird dies in ungefähr 0,5 s kompiliert und läuft in null Zeit für %code% . Ihr "konventioneller" Ansatz kompiliert in etwa 0,4 Sekunden und läuft in 0,8 Sekunden. Nur zur Überprüfung, das Ergebnis ist %code% right?

Als ich Ihre eigene %code% -Lösung ausprobiert habe, habe ich den Compiler ein paar Minuten lang laufen lassen und dann gestoppt, weil anscheinend der Speicher voll war (der Computer begann zu frieren). Der Compiler hat versucht, das Endergebnis zu berechnen, und Ihre Implementierung ist extrem ineffizient, um zur Kompilierzeit verwendet zu werden.

Bei dieser Lösung werden Template-Instanziierungen bei %code% , %code% bei der Instanziierung von %code% wiederverwendet. Daher ist %code% zur Kompilierzeit tatsächlich als Wert bekannt, und zur Laufzeit gibt es nichts zu tun. Dies ist ein dynamischer Programmieransatz und natürlich können Sie das auch zur Laufzeit tun, wenn Sie alle Werte bei %code% bis %code% speichern, bevor Sie bei %code% rechnen.

Mit Ihrer Lösung kann der Compiler %code% zur Kompilierzeit auswerten, ist aber nicht erforderlich für . In Ihrem Fall bleibt die gesamte oder ein Teil der Berechnung für die Laufzeit übrig. In meinem Fall wird die gesamte Berechnung zur Kompilierungszeit versucht und daher nie beendet.

    
___ qstnhdr ___ Berechne die Fibonacci-Zahl (rekursive Methode) in der Kompilierzeit (constexpr) in C ++ 11 ___ answer22645844 ___

Der Grund ist, dass Ihre Runtime-Lösung nicht optimal ist. Für jede Knotennummer werden Funktionen mehrmals aufgerufen. Die Fibonacci-Sequenz hat überlappende Teilprobleme, also zum Beispiel %code% ruft %code% auf, und %code% ruft auch %code% auf.

Der auf Vorlagen basierende Ansatz verwendet (unabsichtlich) einen Ansatz der dynamischen Programmierung, was bedeutet, dass er Werte für zuvor berechnete Zahlen speichert, um Wiederholungen zu vermeiden. Wenn also %code% %code% aufruft, wurde die Anzahl bereits berechnet, wenn %code% dies tat.

Ich empfehle, nach "dynamic programming fibonacci" zu suchen und versuche das, es sollte die Dinge dramatisch beschleunigen.

    
___ answer22646987 ___

Vielleicht verwenden Sie einfach einen effizienteren Algorithmus?

%Vor%

Mein Code basiert auf einer Idee, die D. Knuth im ersten Teil seiner "The Art of Computer Programming" beschrieben hat. Ich kann mich nicht an den genauen Ort in diesem Buch erinnern, aber ich bin sicher, dass der Algorithmus dort beschrieben wurde.

    
___ 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. ___ tag123c11 ___ C ++ 11 ist eine 2011 verabschiedete Version des C ++ - Sprachstandards. Sie hat viele Änderungen und Ergänzungen in der Kernsprache sowie der verbesserten und erweiterten C ++ - Standardbibliothek vorgenommen. ___ tag123recursion ___ Rekursion ist eine Art Funktionsaufruf, bei dem sich eine Funktion selbst aufruft. Solche Funktionen werden auch rekursive Funktionen genannt. Strukturelle Rekursion ist eine Methode zur Problemlösung, bei der die Lösung eines Problems von Lösungen für kleinere Instanzen des gleichen Problems abhängt. ___ tag123templates ___ Das Templates-Tag wird in mehreren Kontexten verwendet: generische Programmierung (insbesondere C ++) und Daten- / Dokumentgenerierung mithilfe von Template-Engines. ___ tag123fibonacci ___ Die Fibonacci-Sequenz ist die durch F (0) = 0, F (1) = 1, F (n + 2) = F (n) + F (n + 1) definierte Sequenz. Die ersten paar Begriffe sind 0, 1, 1, 2, 3, 5, 8. ___ answer22647677 ___

Wenn Sie -0 (oder höher) zu GCC4.8.1 hinzufügen, wird fibonacci & lt; 40 & gt; () eine Kompilierzeitkonstante und der gesamte vorlagengenerierte Code wird aus Ihrer Assembly verschwinden. Der folgende Code

%Vor%

führt zur Assembly-Ausgabe

%Vor%

Dies bietet die beste Laufzeitleistung.

Es sieht jedoch so aus, als würden Sie ohne Optimierungen (-O0) bauen, so dass Sie etwas ganz anderes bekommen. Die Assembly-Ausgabe für jede der 40 Fibonacci-Funktionen sieht grundsätzlich identisch aus (mit Ausnahme der Fälle 0 und 1)

%Vor%

Das ist einfach, es richtet den Stapel ein, ruft die beiden anderen Fibonacci-Funktionen auf, addiert den Wert, reißt den Stapel herunter und kehrt zurück. Keine Verzweigung und keine Vergleiche.

Vergleichen Sie das nun mit der Baugruppe aus dem konventionellen Ansatz

%Vor%

Jedes Mal, wenn die Funktion aufgerufen wird, muss überprüft werden, ob N 0 oder 1 ist und entsprechend handeln. Dieser Vergleich wird in der Template-Version nicht benötigt, da er über die Magie von Templates in die Funktion eingebaut ist. Meine Vermutung ist, dass die unoptimierte Version des Vorlagencodes schneller ist, weil Sie diese Vergleiche vermeiden und auch keine verpassten Verzweigungsvorhersagen haben.

    
___
Mantosh Kumar 25.03.2014, 20:26
quelle

4 Antworten

12

Versuchen Sie Folgendes:

%Vor%

Mit clang und -Os wird dies in ungefähr 0,5 s kompiliert und läuft in null Zeit für N=40 . Ihr "konventioneller" Ansatz kompiliert in etwa 0,4 Sekunden und läuft in 0,8 Sekunden. Nur zur Überprüfung, das Ergebnis ist 102334155 right?

Als ich Ihre eigene constexpr -Lösung ausprobiert habe, habe ich den Compiler ein paar Minuten lang laufen lassen und dann gestoppt, weil anscheinend der Speicher voll war (der Computer begann zu frieren). Der Compiler hat versucht, das Endergebnis zu berechnen, und Ihre Implementierung ist extrem ineffizient, um zur Kompilierzeit verwendet zu werden.

Bei dieser Lösung werden Template-Instanziierungen bei N-2 , N-1 bei der Instanziierung von N wiederverwendet. Daher ist fibonacci<40> zur Kompilierzeit tatsächlich als Wert bekannt, und zur Laufzeit gibt es nichts zu tun. Dies ist ein dynamischer Programmieransatz und natürlich können Sie das auch zur Laufzeit tun, wenn Sie alle Werte bei 0 bis N-1 speichern, bevor Sie bei N rechnen.

Mit Ihrer Lösung kann der Compiler fibonacci<N>() zur Kompilierzeit auswerten, ist aber nicht erforderlich für . In Ihrem Fall bleibt die gesamte oder ein Teil der Berechnung für die Laufzeit übrig. In meinem Fall wird die gesamte Berechnung zur Kompilierungszeit versucht und daher nie beendet.

    
iavr 25.03.2014, 20:45
quelle
5

Der Grund ist, dass Ihre Runtime-Lösung nicht optimal ist. Für jede Knotennummer werden Funktionen mehrmals aufgerufen. Die Fibonacci-Sequenz hat überlappende Teilprobleme, also zum Beispiel fib(6) ruft fib(4) auf, und fib(5) ruft auch fib(4) auf.

Der auf Vorlagen basierende Ansatz verwendet (unabsichtlich) einen Ansatz der dynamischen Programmierung, was bedeutet, dass er Werte für zuvor berechnete Zahlen speichert, um Wiederholungen zu vermeiden. Wenn also fib(5) fib(4) aufruft, wurde die Anzahl bereits berechnet, wenn fib(6) dies tat.

Ich empfehle, nach "dynamic programming fibonacci" zu suchen und versuche das, es sollte die Dinge dramatisch beschleunigen.

    
imreal 25.03.2014 20:44
quelle
0

Wenn Sie -0 (oder höher) zu GCC4.8.1 hinzufügen, wird fibonacci & lt; 40 & gt; () eine Kompilierzeitkonstante und der gesamte vorlagengenerierte Code wird aus Ihrer Assembly verschwinden. Der folgende Code

%Vor%

führt zur Assembly-Ausgabe

%Vor%

Dies bietet die beste Laufzeitleistung.

Es sieht jedoch so aus, als würden Sie ohne Optimierungen (-O0) bauen, so dass Sie etwas ganz anderes bekommen. Die Assembly-Ausgabe für jede der 40 Fibonacci-Funktionen sieht grundsätzlich identisch aus (mit Ausnahme der Fälle 0 und 1)

%Vor%

Das ist einfach, es richtet den Stapel ein, ruft die beiden anderen Fibonacci-Funktionen auf, addiert den Wert, reißt den Stapel herunter und kehrt zurück. Keine Verzweigung und keine Vergleiche.

Vergleichen Sie das nun mit der Baugruppe aus dem konventionellen Ansatz

%Vor%

Jedes Mal, wenn die Funktion aufgerufen wird, muss überprüft werden, ob N 0 oder 1 ist und entsprechend handeln. Dieser Vergleich wird in der Template-Version nicht benötigt, da er über die Magie von Templates in die Funktion eingebaut ist. Meine Vermutung ist, dass die unoptimierte Version des Vorlagencodes schneller ist, weil Sie diese Vergleiche vermeiden und auch keine verpassten Verzweigungsvorhersagen haben.

    
Rastaban 25.03.2014 22:30
quelle
-1

Vielleicht verwenden Sie einfach einen effizienteren Algorithmus?

%Vor%

Mein Code basiert auf einer Idee, die D. Knuth im ersten Teil seiner "The Art of Computer Programming" beschrieben hat. Ich kann mich nicht an den genauen Ort in diesem Buch erinnern, aber ich bin sicher, dass der Algorithmus dort beschrieben wurde.

    
alexeibs 25.03.2014 21:46
quelle