GCC: Statische Optimierungen
« | 05 Feb 2023 | »Statische Bibliotheken gehören bekanntlich zu meinen Lieblingen, und auf Plattformen wie DOS sind sie sowieso eine Notwendigkeit.
Doch trotz Link-Time-Optimization und anderem Schnickschnack sind die fertigen Programme meist größer als unter Windows. Und teilweise sogar verdammt übergroß.
Ein moderner MSVC erzeugt
gerne eine 100 KB große EXE für ein einfaches hello_world
Programm, wenn
die C-Runtime statisch gelinkt wird.
Der GCC unter
Linux oder OpenWatcom
für DOS sind da schon kompakter mit 10 bis 40 KB.
Das ist auch halbwegs “OK”, wenn man an die vielen Patches und
System-Call Wrapper denkt.
Trotzdem fällt mir auf, dass MSVC
Binaries gerade beim GATE Projekt
nicht so rasant anwachsen, wie auf anderen Plattformen.
Sowohl der GCC wie auch OpenWatcom produzieren gleich 200 KB große
Binaries, wenn sie gegen die statische GATE-Core Bibliothek linken.
Ein Blick in die Binaries erklärt das auch:
Da waren alle Funktionen der GATE Bibliotheken enthalten.
Während der MSVC
nur die benutzten Funktionen in die EXE packt,
legen GCC und Watcom auch alle unbenutzten Funktionen bei.
Es wird also Zeit für ein Build-Update.
GCC
Jede nicht statische (also extern
) Funktion wird im GCC als “öffentlich”
markiert. Das bedeutet in .so
Shared Objects,
dass sie exportiert wird.
Programme exportieren jedoch nichts, sondern rufen nur jene Ketten von
Funktionen auf, die irgendwo in main()
anfangen.
Doch da der GCC auch alles unbenutzte beilegt, werden Programme unnötig
groß.
Lösung: attribute ((visibility (“hidden”)))
Ich baue Shared-Libs grundsätzlich mit der default-Visibility hidden
, doch
bei Programmen habe ich das bisher nie benutzt.
Die Alternative ist natürlich, dass man das “Exportier-Makro” aller Funktionen
explizit auf __attribute__ ((visibility ("hidden")))
schaltet.
Watcom
Unter DOS war die Sache noch viel auffälliger. Ich musste das Speicher-Modell
auf Medium
erhöhen, damit alle Daten in einer GATE-Hello-World EXE Platz
hatten.
Auch hier zeigte die erstellte .map
Datei, dass alle Funktionen der
Bibliothek samt Daten und VTBL
Strukturen in die EXE gewandert waren.
Lösung: -zm
und opt el
Wenn man das Compiler-Flag -zm
anhängt, werden Funktionen in eigene Segmente
ausgelagert. Und die Linker-Option ELIMINATE
, löscht dann alle diese
Fragmente, die nicht vom Programm aus aufgerufen werden.
In CMake für Watcom sieht dass dann so aus:
Fazit
Kleine EXE, große Wirkung!
Gerade, wenn man OpenSSL oder libSSH2 statisch in sein Programm mit aufnimmt, kommen hunderte wenn nicht tausende Funktionen hinzu, die man nie braucht. Alle bekommt man leider nie weg, weil sie oft in Strukturen zusammengefasst werden, aber wenn man zumindest einige “eliminieren” kann, schrumpft das finale Programm dann schon ordentlich.
So wurde meine DOS-EXE von 135 KB auf 35 KB reduziert.
Und der GCC verbuchte für das alte 200 KB Binary nach der Kur nur noch 25 KB.
Damit wurde der MSVC
mit seinen 100 KB Babyspeck deutlich geschlagen und die
großen 4-MB Programme sind jetzt auf Windows und Linux ziemlich gleich groß.
Wenn also etwas unnatürlich groß aus der Compiler-Fertigung herauskommt, gilt:
Nicht verzagen, Compiler-Handbuch fragen!
Denn dort findet man (nach langem Suchen) meist den Grund und die Lösung.