Wie optimieren C ++ - Compiler Template-Code?

8

Wie vermeiden Compiler lineares Wachstum in der Größe der kompilierten Binärdatei bei jeder neuen Typisierung einer Vorlage?

>

Ich sehe nicht, wie wir vermeiden können, eine Kopie des Vorlagencodes zu erstellen, wenn eine neue Instanziierung verwendet wird.

Ich denke, dass Kompilierungszeiten und binäre Größen für alle außer den einfachsten Vorlagen in einer relativ großen Codebasis extrem unhandlich werden würden. Aber ihre Verbreitung deutet darauf hin, dass Compiler in der Lage sind, Magie zu praktizieren, um sie praktisch zu machen.

    
tskuzzy 11.12.2013, 20:28
quelle

3 Antworten

6

Viele Template-Funktionen sind klein genug, um effektiv inline zu arbeiten, sodass tun lineares Wachstum in der Binärdatei erhält - aber es ist nicht mehr, als Sie mit äquivalenten Nicht-Template-Funktionen erhalten würden.

Die eine Definitionsregel ist hier wichtig, da der Compiler davon ausgehen kann, dass jede Template-Instanziierung mit identischen Template-Parametern identischen Code generiert. Wenn festgestellt wird, dass die Vorlagenfunktion bereits früher in einer Quelldatei instanziiert wurde, kann sie diese Kopie verwenden, anstatt eine neue zu erstellen. Namemangling ermöglicht es einem Linker, dieselbe Funktion aus verschiedenen kompilierten Quellen zu erkennen. Nichts davon ist garantiert, da Ihr Programm nicht in der Lage sein sollte, den Unterschied zwischen identischen Kopien einer Funktion zu unterscheiden, aber Compiler machen täglich höhere Optimierungen als diese.

Das einzige Mal, wenn Duplikate herausgefiltert werden müssen, wenn eine Funktion eine statische Variable enthält - es kann nur eine Kopie geben. Dies kann jedoch entweder durch Herausfiltern der doppelten Funktionen oder durch Ausfiltern der statischen Variablen selbst erreicht werden.

    
Mark Ransom 11.12.2013 20:47
quelle
5

Es gibt mehrere Dinge, die dazu führen, dass mehrere Instanziierungen für die exacutable Größe nicht zu schädlich sind:

  1. Viele Vorlagen leiten nur Dinge an eine andere Ebene weiter. Obwohl es ziemlich viel Code gibt, verschwindet es meistens, wenn der Code instanziiert und inline wird. Hinweis Das Inlining [und einige Optimierungen] können jedoch leicht zu größerem Code führen. Beachten Sie, dass das Inlining von kleinen Funktionen oft zu kleinerem (und schnellerem) Code führt (im Grunde, weil die ansonsten notwendige Aufrufsequenz oft mehr Anweisungen benötigt als was inline ist und der Optimierer besser wird Chance, den Code durch eine ganzheitlichere Sicht auf das, was vor sich geht, weiter zu reduzieren.
  2. Wenn Vorlagencode nicht inline ist, müssen doppelte Instanziierungen in verschiedenen Übersetzungseinheiten in nur einer Instanziierung zusammengeführt werden. Ich bin kein Linker-Experte, aber mein Verständnis ist, dass z. B. ELF verschiedene Abschnitte verwendet und der Linker wählen kann, nur die Abschnitte aufzunehmen, die tatsächlich verwendet werden.
  3. In größeren ausführbaren Dateien werden einige Vokabeltypen und Instanziierungen benötigt, die an vielen Stellen verwendet und effektiv geteilt werden. Alles zu tun mit einem benutzerdefinierten Typ wäre eine schlechte Idee und Typ löschen ist sicherlich ein wichtiges Werkzeug, um zu viele Arten zu vermeiden.

Das heißt, wo es möglich ist, zahlt es sich aus, Vorlagen vorzuinstantiieren, besonders wenn es nur eine kleine Anzahl von Instanzen gibt, die allgemein verwendet werden. Ein gutes Beispiel ist die IOStreams-Bibliothek, die wahrscheinlich nicht mit mehr als 4 Typen verwendet wird (normalerweise wird sie mit nur einem verwendet): Das Verschieben der Template-Definitionen und ihrer Instanziierungen in separate Übersetzungseinheiten mindert zwar nicht die Größe der ausführbaren Datei, reduziert aber sicher die Kompilierzeit! Beginnend mit C ++ 11 ist es möglich, Template-Instanziierungen als extern zu deklarieren, wodurch die Definitionen sichtbar werden, ohne implizit in Spezialisierungen instanziiert zu werden, von denen bekannt ist, dass sie an anderer Stelle instanziiert werden.

    
Dietmar Kühl 11.12.2013 20:50
quelle
3

Ich glaube, Sie verstehen falsch, wie Vorlagen implementiert werden. Vorlagen werden nach Bedarf in eine entsprechende Klasse / Funktion kompiliert.

Betrachten Sie den folgenden Code ...

%Vor%

Kompilierend das, ich bekomme die folgende Versammlung.

%Vor%

Sie werden feststellen, dass es nur die Hauptfunktion enthält. Jetzt aktualisiere ich meinen Code, um die Template-Funktion zu verwenden.

%Vor%

Kompilieren, dass ich eine viel längere Assembly-Ausgabe einschließlich der Template-Funktion bekomme, um Doubles zu behandeln. Der Compiler sah, dass die Template-Funktion vom Typ "double" verwendet wurde, also wurde eine Funktion für diesen Fall erstellt.

%Vor%

Nehmen wir an, ich ändere den Code, um diese Funktion zweimal zu verwenden.

%Vor%

Sehen wir uns die Baugruppe an, die sie erstellt. Es ist vergleichbar mit der vorherigen Ausgabe, da der Großteil dieses Codes nur der Compiler war, der die Funktion mymax erzeugt, bei der "Type" in ein Double geändert wird. Egal wie oft ich diese Funktion verwende, sie wird nur einmal deklariert.

%Vor%

Im Grunde genommen beeinflussen Templates die exec-Größe nicht mehr als das Schreiben der Funktionen von Hand. Es ist nur eine Annehmlichkeit. Der Compiler erstellt eine Funktion für eine oder mehrere Verwendungen eines bestimmten Typs. Wenn ich ihn also 1 oder 1000 Mal verwende, wird es nur eine Instanz geben. Wenn ich nun meinen Code aktualisiere, um auch einen neuen Typ wie Floats zu behandeln, bekomme ich eine andere Funktion in meiner ausführbaren Datei, aber nur eine, egal wie oft ich diese Funktion verwende.

    
voodoogiant 11.12.2013 20:47
quelle

Tags und Links