Gründe für Codeaufblähung mit C++-Templates



  • CStoll schrieb:

    Erste Lösung wäre es natürlich, weniger Pointer(klassen) durch den Raum zu werfen. Als Alternative würde sich tatsächlich ein vector<void*> als Lösung anbieten (eventuell mit einer Wrapperklasse drumherum, um die Typsicherheit zu garantieren).

    Bei wie gesagt ca. 3000 Sourcefiles kommt das leider nicht in Frage.

    @Artchi:
    WPA (Whole Program Analysis) habe ich ja schon versucht, durch die aktivierte Linker-Optimierung. Aber wie gesagt, zwei Stunden Link-Zeit? Das ist für produktives Arbeiten nicht akzeptabel. Ein typedef sollte per se nichts bringen, da es ja immernoch genau den selben Typ bezeichnet und keinen neuen erzeugt.

    Wie gesagt, ich glaube nicht, dass überall wo vector<int> benutzt wird, eine neue Instanziierung erzeugt wird. Der g++ schreibt hierfür übrigens spezielle Anweisungen für den GNU ld in die Objekt-Files. Per Schalter lassen sich diese Anweisungen auch in separate Files schreiben. (.rpo glaube ich) Diese wertet ld dann aus und sorgt dann dafür, dass jede Instanziierung nur einmal vorkommt. Ein guter Hinweis dafür ist übrigens, dass die Mangeled Names ein gnu_once enthalten. Wie schon im obigen Beitrag gefragt, wie kann ich alle Symbole einer Exedatei sehen? Bei Bibliotheken eigenet sich nm, bei Executables funktioniert der aber nicht mehr.

    Der g++ ist übrigens in Version 3.3.6, mit den dazu passenden GNU Tools.



  • 7H3 N4C3R schrieb:

    CStoll schrieb:

    Erste Lösung wäre es natürlich, weniger Pointer(klassen) durch den Raum zu werfen. Als Alternative würde sich tatsächlich ein vector<void*> als Lösung anbieten (eventuell mit einer Wrapperklasse drumherum, um die Typsicherheit zu garantieren).

    Bei wie gesagt ca. 3000 Sourcefiles kommt das leider nicht in Frage.

    3000 Sourcefiles? Woran arbeitest du denn?

    Du könntest dir natürlich eine eigene Template-Spezialisierung "template<typename T> vector<T*>" schreiben und dann überall dort einbinden, wo vector<T*> vorkommt.

    Wie schon im obigen Beitrag gefragt, wie kann ich alle Symbole einer Exedatei sehen? Bei Bibliotheken eigenet sich nm, bei Executables funktioniert der aber nicht mehr.

    Ich vermute mal, gar nicht - der Linker benötigt noch die Symbole, um zwischen den einzelnen Übersetzungseinheiten Verbindungen herzustellen, in der fertigen EXE bleibt davon nur ein jmp-Befehl übrig.



  • 7H3 N4C3R schrieb:

    @Artchi:
    WPA (Whole Program Analysis) habe ich ja schon versucht, durch die aktivierte Linker-Optimierung. Aber wie gesagt, zwei Stunden Link-Zeit? Das ist für produktives Arbeiten nicht akzeptabel.

    Das macht man ja auch nicht während des Arbeitens. Wenn du entwickelst, schaltet man ja die optimierungen aus. Wenn du ein Release für den Enduser machst, schmeisst man alle Optimierungen ein. Und das builden lässt man dann auch auf einem Build-Rechner laufen und nicht auf dem des Entwicklers. Habt ihr kein autom. Buildsystem, das zu bestimmten Zeiten einen Build startet? Z.B. alle paar Stunden immer wieder oder einmal nachts.

    Und wenn ich dich richtig verstehe, wird die Exe kleiner, wenn du WPA einschaltest?



  • CStoll schrieb:

    Du könntest dir natürlich eine eigene Template-Spezialisierung "template<typename T> vector<T*>" schreiben und dann überall dort einbinden, wo vector<T*> vorkommt.

    Das ist leider nicht standardkonform.

    Selbst bei einer Lösung die auf allen Plattformen funktioniert, müsste man immernoch einen Großteil der Sourcefiles anfassen. 😞 Ob ich sagen darf, um was für eine Anwendung es geht, weiß ich garnicht. 🙂 Geschäftsgeheimnisse und so... steht zumindest in meinem Vertrag. 🙂

    CStoll schrieb:

    Ich vermute mal, gar nicht - der Linker benötigt noch die Symbole, um zwischen den einzelnen Übersetzungseinheiten Verbindungen herzustellen, in der fertigen EXE bleibt davon nur ein jmp-Befehl übrig.

    Die Symbole sind auf jeden Fall noch da. Wie sollte sonst ein Debugger mit der Exe arbeiten. 😉 Auf jeden Fall bei einem Debug-Executable.



  • Artchi schrieb:

    Das macht man ja auch nicht während des Arbeitens. Wenn du entwickelst, schaltet man ja die optimierungen aus. Wenn du ein Release für den Enduser machst, schmeisst man alle Optimierungen ein. Und das builden lässt man dann auch auf einem Build-Rechner laufen und nicht auf dem des Entwicklers. Habt ihr kein autom. Buildsystem, das zu bestimmten Zeiten einen Build startet? Z.B. alle paar Stunden immer wieder oder einmal nachts.

    Ein Buildsystem haben wir selbstverständlich. Nur die Tester arbeiten auf Release-Executables. Wenn die erst Mittags oder Abends im Objektworkspace stehen, wird es etwas unangenehm. Und nur für die Auslieferung eines bis dato ungetesteten Schalter zu aktivieren wäre viel zu riskant.

    Artchi schrieb:

    Und wenn ich dich richtig verstehe, wird die Exe kleiner, wenn du WPA einschaltest?

    7H3 N4C3R schrieb:

    Ich habe mal versucht, den GNU ld mit Optimierung laufen zu lassen (in der Hoffnung, er könne per WPA Codeduplikate entfernen sofern vorhanden) - habe das ganze aber abgebrochen, nachdem nach 2 Stunden immernoch kein Executable da war. Das wäre auch keine akzepable Zeit für einen Link (ganz eventuell für's Release-Exe).



    • nm kann man auch auf ausführbare Programme anwenden. Wenn die Symbole gestrippt sind, bekommt man nur noch die dynamischen mit nm -D
    • Doppelte Instanzierungen, wie sie über mehrere Übersetzungseinheiten hinweg entstehen können sind erstmal doppelt vorhanden. Die Symbole sind aber weak. Der Linker nimmt dann nur eines für die fertige Datei.


  • Versuch mal das folgende Programm auf dein Binary anzuwenden:

    http://zigzag.cs.msu.su/~ghost/blog_data/function_duplicates.cpp

    Es kommt aus folgendem Beitrag:

    http://vladimir_prus.blogspot.com/2005/03/duplicate-function-bodies.html

    Ich hab mal damit rumgespielt und das Größte was ich gesehen habe war eine Einsparung von ungefähr 4000 Bytes bei einer Lib von uns, die sehr viel Templates nutzt.



  • Dankeschön, das werde ich mal ausprobieren. Erfolgsrückmeldung kommt noch.

    Der Beitrag, bzw. auch der Kommentar im Blog klingt aber eher ernüchternd. 😞



  • nm --demangle | uniq -D | wc -l

    sagt gerade mal 22. 😞

    In der Ausgabe sieht man aber in der Tat zigtausende Instanziierungen von vector, construct, destroy und vieles mehr.

    Dem Artikel von ponto nach ist es wohl auch verboten diese zu eliminieren, wenn man C++-standardkonform sein will - selbst wenn die Instanziierungen den selben Maschinencode haben. Denn: Die Adresse eines Members muss für verschiedene Typen immer verschieden sein. Also z.B.

    template <typename T> void dummy() {}
    
    int main()
    {
      void( *p1)() = dummy<void*>;
      void( *p2)() = dummy<int*>;
      assert( p1 != p2);
    }
    

    Da schaut's wohl schlecht aus mit automatischer Optimierung. 😞 Evtl. doch void*-Vectoren mit einer Thunk-Klasse davor.



  • 7H3 N4C3R schrieb:

    nm --demangle | uniq -D | wc -l

    sagt gerade mal 22. 😞

    In der Ausgabe sieht man aber in der Tat zigtausende Instanziierungen von vector, construct, destroy und vieles mehr.

    Dem Artikel von ponto nach ist es wohl auch verboten diese zu eliminieren, wenn man C++-standardkonform sein will - selbst wenn die Instanziierungen den selben Maschinencode haben. Denn: Die Adresse eines Members muss für verschiedene Typen immer verschieden sein. Also z.B.

    template <typename T> void dummy() {}
    
    int main()
    {
      void( *p1)() = dummy<void*>;
      void( *p2)() = dummy<int*>;
      assert( p1 != p2);
    }
    

    Da schaut's wohl schlecht aus mit automatischer Optimierung. 😞 Evtl. doch void*-Vectoren mit einer Thunk-Klasse davor.

    Du kannst das Tool nicht auf statische Binaries anwerfen. Du musst Libs nehmen, die mit -fPIC kompiliert wurden. Du kannst ja einfach das ganze Programm als Lib zusammenfügen und dann testen.


Anmelden zum Antworten