wake-up-neo.net

G ++ - Optimierung über -O3/-Ofast hinaus

Das Problem

Wir haben ein mittelgroßes Programm für eine Simulationsaufgabe, das wir optimieren müssen. Wir haben unser Bestes getan, um die Quelle bis zur Grenze unserer Programmierkenntnisse zu optimieren, einschließlich der Profilerstellung mit Gprof und Valgrind .

Wenn der Vorgang abgeschlossen ist, möchten wir das Programm wahrscheinlich für einige Monate auf mehreren Systemen ausführen. Deshalb sind wir wirklich daran interessiert, die Optimierung an ihre Grenzen zu bringen. 

Auf allen Systemen wird Debian/Linux mit relativ neuer Hardware (Intel i5 oder i7) ausgeführt. 

Die Frage

Was sind mögliche Optimierungsoptionen für eine aktuelle Version von g ++, die über -O3/-Ofast hinausgehen?

Wir sind auch an kostspieligen kleineren Optimierungen interessiert, die sich langfristig auszahlen werden. 

Was wir gerade verwenden

Im Moment verwenden wir die folgenden g ++ Optimierungsoptionen:

  • -Ofast: Höchste "Standard" -Optimierungsstufe. Der mitgelieferte -ffast-math hat bei unseren Berechnungen keine Probleme verursacht. Daher haben wir uns trotz der Nichtkonformität für die Norm entschieden.
  • -march=native: Ermöglicht die Verwendung aller CPU-spezifischen Anweisungen.
  • -flto zur Ermöglichung der Verbindungszeitoptimierung über verschiedene Kompilationseinheiten hinweg.
49
Haatschii

Die meisten Antworten schlagen alternative Lösungen vor, wie z. B. verschiedene Compiler oder externe Bibliotheken, die höchstwahrscheinlich viel Umschreibungs- oder Integrationsarbeit erfordern würden. Ich werde versuchen, mich auf die Frage zu beschränken und mich darauf zu konzentrieren, was mit GCC allein getan werden kann, indem ich Compiler-Flags aktiviere oder minimale Änderungen am Code vornehme, wie vom OP gefordert. Dies ist keine "Sie müssen dies tun" -Antwort, sondern vielmehr eine Sammlung von GCC-Optimierungen, die für mich gut funktioniert haben und die Sie ausprobieren können, wenn sie in Ihrem spezifischen Kontext relevant sind.


Warnungen bezüglich der ursprünglichen Frage

Bevor wir auf die Details eingehen, lesen wir einige Warnungen bezüglich der Frage, normalerweise für Leute, die mitkommen, und sagen: "Das OP optimiert über O3 hinaus, ich sollte dieselben Flags verwenden wie er!".

  • -march=native Ermöglicht die Verwendung von Befehlen, die für eine bestimmte CPU-Architektur spezifisch sind und für eine andere Architektur nicht unbedingt verfügbar sind. Das Programm funktioniert möglicherweise überhaupt nicht, wenn es auf einem System mit einer anderen CPU ausgeführt wird oder wesentlich langsamer ist (da dies auch mtune=native Aktiviert). Beachten Sie dies, wenn Sie es verwenden möchten. Weitere Informationen hier .
  • -Ofast Aktiviert, wie Sie angegeben haben, einige nicht standardkonforme Optimierungen. Daher sollte es auch mit Vorsicht verwendet werden. Weitere Informationen hier .

Andere GCC-Flags zum Ausprobieren

Die Details für die verschiedenen Flags sind aufgelistet hier .

  • -Ofast Aktiviert -ffast-math, Wodurch wiederum -fno-math-errno, -funsafe-math-optimizations, -ffinite-math-only, -fno-rounding-math, -fno-signaling-nans und -fcx-limited-range. Sie können die Optimierung der Gleitkommaberechnung noch weiter fortsetzen , indem Sie selektiv einige zusätzliche Flags hinzufügen, z. B. -fno-signed-zeros, -fno-trapping-math Und andere. Diese sind nicht in -Ofast Enthalten und können zu zusätzlichen Leistungssteigerungen bei Berechnungen führen. Sie müssen jedoch prüfen, ob sie Ihnen tatsächlich zugute kommen und keine Berechnungen unterbrechen.
  • GCC bietet auch eine große Anzahl von anderen Optimierungsflags , die durch keine "-O" -Optionen aktiviert werden. Sie werden als "experimentelle Optionen, die möglicherweise fehlerhaften Code erzeugen" aufgeführt. Sie sollten daher ebenfalls mit Vorsicht verwendet und ihre Auswirkungen sowohl durch Testen auf Richtigkeit als auch durch Benchmarking überprüft werden. Trotzdem verwende ich oft -frename-registers, Diese Option hat für mich nie zu unerwünschten Ergebnissen geführt und führt tendenziell zu einer spürbaren Leistungssteigerung (dh kann beim Benchmarking gemessen werden). Dies ist jedoch die Art von Flag, die stark von Ihrem Prozessor abhängt. -funroll-loops Liefert auch manchmal gute Ergebnisse (und impliziert auch -frename-registers), Aber es hängt von Ihrem tatsächlichen Code ab.

[~ # ~] pgo [~ # ~]

GCC verfügt über profilgeführte Optimierungsfunktionen . Es gibt nicht viel genaue GCC-Dokumentation darüber, aber dennoch ist es ziemlich einfach, es zum Laufen zu bringen.

  • kompilieren Sie zuerst Ihr Programm mit -fprofile-generate.
  • lassen Sie das Programm laufen (die Ausführungszeit wird erheblich langsamer, da der Code auch Profilinformationen in .gcda-Dateien generiert).
  • kompilieren Sie das Programm erneut mit -fprofile-use. Wenn Ihre Anwendung über mehrere Threads verfügt, fügen Sie auch das Flag -fprofile-correction Hinzu.

PGO mit GCC kann erstaunliche Ergebnisse liefern und die Leistung erheblich steigern (ich habe bei einem der Projekte, an denen ich kürzlich gearbeitet habe, eine Geschwindigkeitssteigerung von 15-20% festgestellt). Offensichtlich geht es hier darum, einige Daten zu haben, die ausreichend repräsentativ für die Ausführung Ihrer Anwendung sind und nicht immer verfügbar oder leicht zu beschaffen sind.

GCC's Parallel Mode

GCC bietet einen Parallelen Modus , der erstmals zu der Zeit veröffentlicht wurde, als der GCC 4.2-Compiler noch nicht verfügbar war.

Grundsätzlich bietet es Ihnen parallele Implementierungen vieler Algorithmen in der C++ Standard Library . Um sie global zu aktivieren, müssen Sie dem Compiler lediglich die Flags -fopenmp Und -D_GLIBCXX_PARALLEL Hinzufügen. Sie können bei Bedarf auch jeden Algorithmus einzeln aktivieren, dies erfordert jedoch einige geringfügige Codeänderungen.

Alle Informationen zu diesem Parallelmodus finden Sie hier .

Wenn Sie diese Algorithmen häufig für große Datenstrukturen verwenden und über viele verfügbare Hardware-Thread-Kontexte verfügen, können diese parallelen Implementierungen zu einer enormen Leistungssteigerung führen. Bisher habe ich nur von der parallelen Implementierung von sort Gebrauch gemacht, aber um eine ungefähre Vorstellung zu vermitteln, konnte ich die Zeit für das Sortieren in einer meiner Anwendungen von 14 auf 4 Sekunden reduzieren (Testumgebung: Vektor von 100) Millionen Objekte mit angepasster Komparatorfunktion und 8-Kerne-Maschine).

Zusätzliche Tricks

Im Gegensatz zu den vorherigen Punkten erfordert dieser Teil einige kleine Änderungen im Code . Sie sind auch GCC-spezifisch (einige von ihnen funktionieren auch auf Clang), daher sollten Makros zur Kompilierungszeit verwendet werden, um den Code auf anderen Compilern portierbar zu halten. Dieser Abschnitt enthält einige fortgeschrittenere Techniken und sollte nicht verwendet werden, wenn Sie keine Ahnung haben, was auf Baugruppenebene vor sich geht. Beachten Sie auch, dass Prozessoren und Compiler heutzutage ziemlich schlau sind. Daher kann es schwierig sein, die hier beschriebenen Funktionen spürbar zu nutzen.

  • GCC-Builtins, die aufgelistet sind hier . Konstrukte wie __builtin_expect Können dem Compiler zu besseren Optimierungen verhelfen, indem sie ihm Informationen zur Verzweigungsvorhersage liefern. Andere Konstrukte wie __builtin_prefetch Bringen Daten in einen Cache, bevor auf sie zugegriffen wird, und können dabei helfen, Cache-Fehler zu reduzieren .
  • funktionsattribute, die aufgelistet sind hier . Insbesondere sollten Sie die Attribute hot und cold untersuchen. Ersteres zeigt dem Compiler an, dass die Funktion ein Hotspot des Programms ist, und optimiert die Funktion aggressiver und platziert sie zur besseren Lokalisierung in einem speziellen Unterabschnitt des Textabschnitts. Letzteres optimiert die Funktion für die Größe und platziert sie in einem anderen speziellen Unterabschnitt des Textabschnitts.

Ich hoffe, diese Antwort wird sich für einige Entwickler als nützlich erweisen, und ich werde gerne Änderungen oder Vorschläge berücksichtigen.

54
Pyves

relativ neue Hardware (Intel i5 oder i7)

Warum nicht in eine Kopie der Intel-Compiler und Hochleistungsbibliotheken investieren? Es kann GCC bei Optimierungen um eine beträchtliche Marge übertreffen, in der Regel von 10% bis 30% oder sogar mehr, und noch mehr für Programme mit hoher Zahl von Zahlen. Außerdem bietet Intel eine Reihe von Erweiterungen und Bibliotheken für hochleistungsfähige (parallele) Anwendungen, wenn dies eine Möglichkeit ist, Sie in Ihren Code zu integrieren. Es kann sich auszahlen, wenn Sie am Ende mehrere Monate Laufzeit sparen.

Wir haben unser Bestes gegeben, um die Quelle bis an die Grenzen unserer Programmierkenntnisse zu optimieren

Meiner Erfahrung nach haben Mikro- und Nano-Optimierungen, die Sie normalerweise mit Hilfe eines Profilers vornehmen, im Vergleich zu Makrooptimierungen (Straffung der Code-Struktur) und vor allem einer geringen Rendite auf Zeitinvestitionen (Return-Time-Investments) gegenüberstehen und oft übersehen werden Speicherzugriffsoptimierungen (z. B. Referenzort, In-Order-Durchquerung, Minimierung der Umleitung, Ausnutzung von Cache-Misses usw.). Letzteres beinhaltet normalerweise das Entwerfen der Speicherstrukturen, um die Verwendung des Speichers (durchlaufen) besser widerzuspiegeln. Manchmal kann es so einfach sein, einen Containertyp zu wechseln und dadurch einen enormen Leistungsschub zu erzielen. Bei Profilern verlieren Sie sich häufig in den Details der Anweisungen für Anweisungen, und Probleme mit dem Speicherlayout werden nicht angezeigt und werden normalerweise übersehen, wenn Sie vergessen, das Gesamtbild zu betrachten. Dies ist ein viel besserer Weg, um Ihre Zeit zu investieren, und die Auszahlungen können enorm sein (zB viele O(logN) - Algorithmen) enden fast so langsam wie O(N), nur weil das Speicherlayout schlecht ist (Die Verwendung einer Linked-List oder eines Linked-Tree ist beispielsweise ein typischer Grund für enorme Leistungsprobleme im Vergleich zu einer zusammenhängenden Speicherstrategie.).

19
Mikael Persson

Wenn Sie es sich leisten können, versuchen Sie VTune . Es bietet VIEL mehr Informationen als einfaches Sampling (soweit ich weiß, von gprof zur Verfügung gestellt). Sie könnten den Code Analyst versuchen. Bei Latter handelt es sich um eine anständige, freie Software, die jedoch möglicherweise nicht richtig (oder überhaupt) mit Intel-CPUs funktioniert.

Wenn Sie mit einem solchen Tool ausgestattet sind, können Sie verschiedene Kennzahlen wie die Cache-Nutzung (und im Wesentlichen das Speicherlayout) überprüfen, die - wenn sie in vollem Umfang genutzt werden - einen enormen Effizienzvorteil bieten.

Wenn Sie sicher sind, dass Ihre Algorithmen und Strukturen optimal sind, sollten Sie auf jeden Fall die mehreren Kerne auf i5 und i7 verwenden. Mit anderen Worten, spielen Sie mit verschiedenen parallelen Programmieralgorithmen mustern herum und sehen Sie, ob Sie eine Beschleunigung erreichen können.

Wenn Sie wirklich parallele Daten haben (Array-ähnliche Strukturen, an denen Sie ähnliche/gleiche Operationen ausführen), sollten Sie die Anweisungen von OpenCL und/- SIMD (einfacher einzurichten) ausprobieren.

6
Red XIII

dann können Sie noch Folgendes versuchen: ACOVEA project: Analyse von Compileroptimierungen über einen evolutionären Algorithmus - Wie aus der Beschreibung ersichtlich, wird ein genetischer Algorithmus verwendet, um die besten Compileroptionen für Ihr Projekt auszuwählen Man kann viele Male zusammenstellen und das Timing überprüfen, dem Algorithmus ein Feedback geben :) - aber die Ergebnisse könnten beeindruckend sein! :)

4
zaufi

Einige Anmerkungen zu der aktuell gewählten Antwort (Ich habe noch nicht genug Rufpunkte, um dies als Kommentar zu posten):

Die Antwort lautet: 

-fassociative-math, -freciprocal-math, -fno-signed-zeros und -fno-trapping-math. Diese sind nicht in -Ofast enthalten und können zu zusätzlichen Leistungssteigerungen bei Berechnungen führen

Vielleicht war dies wahr, als die Antwort gepostet wurde, aber die GCC-Dokumentation besagt, dass alle von -funsafe-math-optimizations aktiviert werden, die von -ffast-math aktiviert wird, was von -Ofast aktiviert wird. Dies kann mit dem Befehl gcc -c -Q -Ofast --help=optimizer überprüft werden, der anzeigt, welche Optimierungen von -Ofast aktiviert werden, und bestätigt, dass alle diese Optionen aktiviert sind.

Die Antwort sagt auch:

andere Optimierungsflags, die nicht durch "-O" -Optionen aktiviert werden ... -frename-registers

Wieder zeigt der obige Befehl, dass -frename-registers zumindest bei meinem GCC 5.4.0 standardmäßig mit -Ofast aktiviert ist.

2
user3708067

Es ist schwierig, ohne weitere Details zu antworten:

  • welche Art von Zahlenknirschen?
  • welche Bibliotheken benutzen Sie?
  • welcher Grad der Parallelisierung?

Können Sie den Teil Ihres Codes aufschreiben, der am längsten dauert? (In der Regel eine enge Schleife)

Wenn Sie an die CPU gebunden sind, ist die Antwort anders als bei der Eingabe von IO. 

Bitte geben Sie weitere Details an. 

1
Escualo

Ich würde empfehlen, einen Blick auf die Art der Operationen zu werfen, die das schwere Heben kostspielig machen, und eine optimierte Bibliothek zu suchen. Es gibt eine Menge schneller, Assembly-optimierter, SIMD-vektorisierter Bibliotheken für allgemeine Probleme (meistens Mathematik). Das Rad neu zu erfinden ist oft verlockend, aber es lohnt sich in der Regel nicht, wenn eine vorhandene Lösung Ihre Bedürfnisse abdeckt. Da Sie nicht angegeben haben, welche Art von Simulation es ist, kann ich nur einige Beispiele liefern.

http://www.yeppp.info/

http://eigen.tuxfamily.org/index.php?title=Main_Page

https://github.com/xianyi/OpenBLAS

0
uLoop