Warum ist Linker Optimierung so schlecht?
-
05-07-2019 - |
Frage
Vor kurzem ein Kollege hat mich darauf hingewiesen, dass alles in einer einzigen Datei zusammenzustellen erstellt viel effizienter Code als separaten Objektdateien kompilieren - auch mit Optimierung Link Zeit eingeschaltet . Darüber hinaus ging die gesamte Kompilierung für das Projekt erheblich. Da einer der Hauptgründe für die Verwendung von C ++ Code-Effizienz ist, war das für mich überraschend.
ist klar, wenn die Archivierungs / Linker eine Bibliothek aus Objektdateien macht, oder verknüpft sie in eine ausführbare Datei, selbst einfache Optimierungen werden bestraft. Im Beispiel unten, trivial inlining kostet 1,8% in der Leistung, wenn sie durch den Linker anstelle des Compilers getan. Es scheint, wie Compiler-Technologie genug vorangetrieben werden soll ziemlich häufig Situationen wie diese zu behandeln, aber es ist nicht passiert.
Hier ist ein einfaches Beispiel mit Visual Studio 2008:
#include <cstdlib>
#include <iostream>
#include <boost/timer.hpp>
using namespace std;
int foo(int x);
int foo2(int x) { return x++; }
int main(int argc, char** argv)
{
boost::timer t;
t.restart();
for (int i=0; i<atoi(argv[1]); i++)
foo (i);
cout << "time : " << t.elapsed() << endl;
t.restart();
for (int i=0; i<atoi(argv[1]); i++)
foo2 (i);
cout << "time : " << t.elapsed() << endl;
}
foo.cpp
int foo (int x) { return x++; }
Ergebnisse des Laufes: 1,8% Leistung getroffen mit verknüpften foo
statt Inline foo2
$ ./release/testlink.exe 100000000
time : 13.375
time : 13.14
Und ja, die Linkeroptimierungsflags (/ LTCG) sind.
Lösung
Ich bin kein Compiler-Spezialist, aber ich denke, der Compiler zur Verfügung viel mehr Informationen zur Verfügung hat zu optimieren, wie es auf einem Sprachbaum arbeitet, wie an den Linker gegenüber, die sich auf dem Objekt Ausgang für den Betrieb zum Inhalt hat, weit weniger ausdrucksvoll als der Code der Compiler gesehen. Daher weniger Aufwand durch Linker und Compiler-Entwicklungsteam (s) in der Herstellung Linker Optimierungen, könnte passen, in der Theorie ausgegeben, die Tricks die Compiler tun.
BTW, ich bin traurig, dass ich Ihre ursprüngliche Frage in die LTCG Diskussion abgelenkt. Ich verstehe jetzt Ihre Frage war ein bisschen anders, mehr mit der Verknüpfungszeit vs. Kompilierung statische Optimierungen möglich / zur Verfügung.
Andere Tipps
Ihre Mitarbeiter sind nicht mehr aktuell. Die Technologie hat sich seit 2003 (auf der MS C ++ Compiler) hier gewesen: / LTCG . Link Timecode-Generierung wird mit genau diesem Problem beschäftigen. Von dem, was ich weiß, die GCC diese Funktion für die nächste Generation Compiler auf dem Radar hat.
LTCG nicht nur den Code zu optimieren, wie Funktionen in Module inlining, aber eigentlich rearanges Code Cache Ort zu optimieren und für eine bestimmte Last Verzweigung finden Sie unter Profil-geführte Optimierung . Diese Optionen sind reserviert usualy nur für Release baut wie die Build-Stunden dauern kann, beenden: eine instrumentierten ausführbaren verknüpfen, eine Profilierung Last laufen und dann wieder verbinden mit den Profilierungs Ergebnisse. Der Link enthält ausführliche Informationen darüber, was genau mit LTCG optimiert:
Inlining - zum Beispiel, wenn es existiert eine Funktion, A, die häufig ruft Funktion B, und die Funktion B ist relativ klein ist, dann das Profil gelenkten Optimierungen Funktion B inline in Funktion A.
Virtual Call Speculation - Wenn ein virtueller Anruf oder anderer Anruf durch eine Funktionszeiger zielt häufig ein bestimmte Funktion, ein Profil-geführtes Optimierung kann einfügen einer bedingt ausgeführten direkten Aufruf die häufig gezielte Funktion und der direkte Aufruf inlined werden kann.
Register Allocation - Optimierung mit Profildaten führt zu einer besseren Registerzuweisung.
Basic Block Optimierung - Basisblock Optimierung ermöglicht häufig ausgeführt Basisblöcke, die zeitlich auszuführen innerhalb eines gegebenen Rahmens in platziert werden der gleiche Satz von Seiten (Lokalität). Diese minimiert die Anzahl der Seiten verwendet wird, somit Speicher-Overhead minimiert wird.
Größe / Performance-Optimierung - Funktionen wo verbringt das Programm viel Zeit kann auf Geschwindigkeit optimiert werden.
Funktion Layout- - Basierend auf den Anruf Graph und Profil Anrufer / Angerufenen Verhalten, das Funktionen neigen dazu, entlang der gleichen Ausführungspfad sind im selben Abschnitt platziert.
Conditional Branch-Optimierung - Mit die Wert-Sonden, Profil-geführte Optimierungen können, wenn ein bestimmte finden Wert in einer Switch-Anweisung wird verwendet, häufiger als andere Werte. Diese Wert kann dann aus dem gezogen werden switch-Anweisung. Das gleiche kann getan werden, mit if / else Anweisungen, wo die Optimierer kann die if / else bestellen so dass entweder der, wenn sonst Block erste Abhängigkeit davon, welcher Block platziert ist häufiger der Fall.
Dead Code Trennung - Code, der ist nicht während der Profilierung genannt wird bewegt auf einen speziellen Abschnitt, der angehängt ist bis zum Ende des Satzes von Abschnitten. Dies hält effektiv diesen Abschnitt aus den häufig verwendeten Seiten.
EH-Code Trennung - Der EH-Code, Sein außergewöhnlich ausgeführt wird, kann oft auf einem separaten Abschnitt bewegt werden wenn Profil-geführte Optimierungen können feststellen, dass die Ausnahmen auftreten nur auf außergewöhnliche Bedingungen.
Speicher Intrinsics - Der Ausbau der intrinsics kann besser, wenn entschieden wird, kann bestimmt werden, wenn ein intrinsisches ist häufig genannt. Eine intrinsische CAN- auch optimiert werden auf dem Block basiert Größe verschiebt oder kopiert.
Ihre Mitarbeiter sind intelligenter als die meisten von uns. Auch wenn es zunächst eine grobe Annäherung scheint, Projekt inlining in eine einzige CPP-Datei hat eine Sache, dass die anderen Ansätze, wie Link-Time-Optimierung hat nicht und wird nicht für während haben - Zuverlässigkeit
Allerdings bat Sie dies vor zwei Jahren, und ich bezeuge, dass viel hat sich seitdem verändert (mit g ++ zumindest). Devirtualization ist viel zuverlässiger, zum Beispiel.