Optimieren verschachtelte if-Anweisungen innerhalb einer Schleife in C / C ++ mit GCC

9

Ich teste verschiedene Optimierungen in C / C ++ mit dem GCC-Compiler. Ich habe derzeit eine Schleife mit mehreren verschachtelten if-Anweisungen. Die Bedingungen werden zu Beginn der Programmausführung berechnet. Es sieht ungefähr so ​​aus:

%Vor%

Wobei doATrueStuff() eine Inline-Funktion ist, die einige einfache numerische Berechnungen durchführt, so dass beim Aufruf kein Overhead entsteht.

Leider können die Bedingungen nicht vorher definiert werden, sie müssen zur Laufzeit berechnet werden. Wir können nicht einmal zuverlässig vorhersagen, ob sie wahr oder falsch sind. getA() könnte auch rand()%2 sein. Aber einmal berechnet, ändert sich ihr Wert nie.

Es gibt zwei Lösungen, an die ich gedacht habe. Eine davon sind globale Funktionszeiger, mit denen die entsprechende Funktion in der Schleife aufgerufen wird:

%Vor%

Auf diese Weise kann ich alle Zweige aus der Schleife entfernen, aber dann werde ich den Overhead von mehreren Funktionsaufrufen haben, die mich verlangsamen.

Oder ich könnte einfach eine andere Schleife für jede Kombination von Bedingungen haben, etwa so:

%Vor%

Aber das ist viel weniger elegant und es wird unmöglich, dies effizient zu tun, sobald man zu viele Bedingungen hat, da man für X-Bedingungen 2 ^ X-Schleifen schreiben muss.

Gibt es eine elegante / schnellere Möglichkeit, dies zu optimieren?

Gibt es überhaupt irgendeinen Punkt in diesem oder wird der Compiler irgendwie verstehen, dass sich die Bedingung während der Schleife nicht ändert und sie selbst optimiert?

Und gibt es aus Neugier eine andere Programmiersprache, die das Schreiben solcher Codes einfacher / möglich macht? Oder wäre das nur möglich, wenn man die Anweisungen des Programms, nachdem es in den Speicher geladen wurde, mit Assembly ändert?

    
Parisbre56 15.05.2015, 16:14
quelle

2 Antworten

2

Die Theorie:

Wenn Sie versuchen, Ihren Code durch ein verrücktes Neuschreiben zu optimieren, kann es für den Compiler schwierig werden, die üblichen Optimierungen vorzunehmen. Der Compiler und auch der Prozessor können den Code mit 2 Techniken optimieren:

  1. Verzweigungsvorhersage: Der Compiler kann dies tun, indem er profilgesteuert verwendet Optimierungen , hauptsächlich durch Schätzung der Wahrscheinlichkeit jeder Verzweigung. Die CPU hat auch Verzweigungszielpuffer, die versuchen, das Verzweigungsmuster zu erkennen, zusätzlich zur Berechnung von Statistiken für jedes Ziel.
  2. Zweigprädiktion: Der Compiler oder die CPU veranlasst, dass der Code beide Zweige parallel ausführt (, weil Prozessoren heutzutage superskalar sind ) und basierend auf dem Ergebnis der Bedingung werden die Ergebnisse ignoriert des falschen Pfades (zB CMOV-Anweisung). Sie können versuchen, die Zweigprädikation zu deaktivieren, indem Sie Folgendes verwenden: -fno-if-conversion und -fno-if-conversion2 . Dies kann hilfreich sein, wenn in jedem Zweig viel Rechenleistung vorhanden ist und die Ausführung aller Pfade zu einer Verschwendung von Befehlsdecodern und Ausführungsports führt.

Als einfacher Entwickler, der gcc verwendet, können Sie die Verzweigungsvorhersage oder Codegenerierung mithilfe der "likely" und "unlikely" Kompilierungshinweise unterstützen. Überprüfen Sie hier für weitere Details. Dies könnte funktionieren, wenn Sie beispielsweise wissen, dass eine Bedingung wahrscheinlicher ist als eine andere.

Um die Effizienz der Verzweigungsvorhersage zu sehen, verwenden Sie perf stat ./binary und prüfen Sie die Verzweigungsfehlgeschlagenheit und die Anzahl der Verzweigungsfehlschläge für jede durchgeführte Optimierung.

In Ihrem Code-Fall:

Wenn conditionA, conditionB und conditionC vor der Schleife berechnet werden und sich nicht ändern, ist es für den Verzweigungsvorhersager leicht, das Muster zu erkennen. Der Prädiktor der CPU macht dies, indem er die letzten Äste, die er / sie nicht genommen hat, im Auge behält, und er verwendet den aufgezeichneten Verlauf, um die folgenden Äste vorherzusagen. Daher erwarte ich tatsächlich sehr wenig Leistungseinbußen aufgrund von Zweigen in Ihrem Code, die Sie wie oben bestätigen können.

    
VAndrei 15.05.2015, 20:34
quelle
2

Betrachten Sie Vorlagen. Die Herausforderung besteht darin, die Laufzeitwerte den Template-Parametern für die Kompilierung zuzuordnen. Der folgende Textbaustein ist eine Dispatch-Funktion pro Parameter, und der Compiler erstellt den Baum der Kombinationen für Sie. Nicht gerade elegant, aber viel besser skalierbar als eine Mehrparameter-Schaltanlage zu öffnen.

Sie können die Template-Parameter (oder deren Funktionen) auch direkt in Ihren Berechnungen verwenden, und diese werden ebenfalls optimiert, z. B. die Auswahl einer Konstanten auf der Basis eines Template-Parameters oder die Multiplikation einer 0 in einen Ausdruck du willst nichts beitragen.

%Vor%     
Peter 15.05.2015 20:12
quelle

Tags und Links