GCC Binaries sind viel zu groß
« | 09 May 2021 | »Schon 2007 am Anfang meiner C++ Liebschaft wunderte ich mich stets darüber, dass Programme, die aus den gleichen Sourcen kompiliert wurden, unter Linux mit GCC Umgebung um einiges größer waren als unter Windows mit MSVC.
Und ja, das manifestiert sich vor allem im Einsatz von statisch
kompilierten Bibliotheken.
Also was kann man dagegen tun?
Update: LTO funktioniert erst ab GCC 7.
Auf älteren Umgebungen gibt es Probleme
Windows EXE und Linux Binaries im Vergleich
Mit heutigem Stichtag sehen das größte und kleinste GATE Demo Projekt so aus:
Programm | Plattform | Größe |
---|---|---|
gatecli.exe | Windows x64 | 1 890 KB |
gatecli | Linux x64 | 2 765 KB |
vtxtedit.exe | Windows x64 | 287 KB |
vtxtedit | Linux x64 | 1 005 KB |
Die statisch kompilierten Bibliotheken sind auf beiden Seiten gleich
(zB. zlib
, libjpeg
oder libreSSL
) und trotzdem ergeben sich
Größenunterschiede vom Faktor 2 bis 3 zwischen den resultierenden Binärdateien.
Natürlich könnte man argumentieren, dass die statisch einkompilierte C
Runtime in Windows nur
ein kleiner Wrapper zur dynamischen System-C-Runtime ist,
während im GCC
größere Funktionssammlungen in die Binärdatei einfließen.
Aber mal im ernst … wenn memcopy()
printf()
, fopen()
und ihre
Geschwister eine 1 Megabytes große Implementierung haben, müsste Linux schon
verdammt ineffizient sein, wenn andere Implementierungen am Mikrocontroller
in wenigen Kilobytes unterkommen.
Einen Unterschied gibt es bei den UI-Tools wie vtxtedit
, das unter Windows
direkt die WinAPI
(kernel
, user32
und gdi32
) nutzt, während das Linux-Kompilat auf
GTK+ 3 zugreift.
GTK
ist zwar dynamisch gelinkt, was aber nicht heißt, dass hier
zusätzliche Dispatcher und Inline-Funktionen zur Geltung kommen.
gatecli
hingegen nutzt ausschließlich nur Konsolen-APIs zur Kommunikation
und alle externen Algorithmen und Features liegen in den gleichen Quellen vor.
Der Grund für die Größenunterschiede sollte also in der “üblichen” Codegenerierung liegen …
GCC Binärausgaben verkleinern
Ich habe daher mal im Netz etwas herumgesucht, was man tun kann, um GCC Binaries zu verkleinern. Folgende Ansätze habe ich gefunden:
-Os
Compiler Option anstatt von-O3
um für geringe Größe zu optimieren.-flto
Compiler und Linker Option um Link-Time-Optimization zu aktivieren.-fdata-sections -ffunction-sections
beim Kompilieren und-Wl,--gc-sections
beim Linken einsetzen um unbenutzten Code und Daten zu entfernen.-s
Compiler und Linker Option zur Entfernung von Debugging-Symbolen aktivieren.
Nun kommt hinzu, dass ich aus Kompatibilitätsgründen nur CMake 3.0 nutze, welches die Link-Time-Optimization noch nicht selbst unterstützt, womit ich alle Compiler und Linker-Flags selbst setzen muss.
Ein Spezialfall ist auch -Os
, denn CMake
fügt automatisch ein -O3
hinten an, was mein -Os
offenbar aufhebt. Daher muss dieses zuerst in
CMake
entfernt werden mit:
string(REPLACE "-O3" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
Nachdem diese Optimierungen nur im Falle eines GCC
Compiler wirksam
werden sollen, sieht mein CMakeLists.txt
Update ungefähr so aus:
1if(CMAKE_COMPILER_IS_GNUCC) 2 string(REPLACE "-O3" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") 3 set(CMAKE_C_FLAGS_RELEASE 4 ${CMAKE_C_FLAGS_RELEASE} 5 -flto -Os -s -fdata-sections -ffunction-sections) 6 set(CMAKE_EXE_LINKER_FLAGS_RELEASE 7 ${CMAKE_EXE_LINKER_FLAGS_RELEASE} 8 -flto -s -Wl,--gc-sections) 9endif(CMAKE_COMPILER_IS_GNUCC) 10if(CMAKE_COMPILER_IS_GNUCXX) 11 string(REPLACE "-O3" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") 12 set(CMAKE_CXX_FLAGS_RELEASE 13 ${CMAKE_CXX_FLAGS_RELEASE} 14 -flto -Os -s -fdata-sections -ffunction-sections) 15 set(CMAKE_EXE_LINKER_FLAGS_RELEASE 16 ${CMAKE_EXE_LINKER_FLAGS_RELEASE} 17 -flto -s -Wl,--gc-sections) 18endif(CMAKE_COMPILER_IS_GNUCXX)
Resultate
Nach einem vollständigen Rebuild nach einem make clean
sieht die
Tabelle jetzt anders aus:
Programm | Plattform | Neue Größe |
---|---|---|
gatecli.exe | Windows x64 | 1 890 KB |
gatecli | Linux x64 | 1 621 KB |
vtxtedit.exe | Windows x64 | 287 KB |
vtxtedit | Linux x64 | 598 KB |
Wow! gatecli
ist unter Linux jetzt sogar kleiner als unter Windows.
Fast könnte man glauben, dass der GCC
jetzt bessere Resultate liefert als
der MSVC
(was im Detail auch stimmen kann), aber hier wirkt sich eher aus,
dass einige Linux-Implementierungen auf meiner Seite nur NOT_IMPLEMENTED
zurückliefern, während unter Windows schon ein paar hundert Zeilen
abgetippt wurden.
Aber mit diesem Ergebnis kann ich mich schon ganz gut anfreunden.
Fazit
Link-Time-Optimierungen sind unter MSVC
schon lange eine Default
Einstellung, die der GCC
jedoch explizit vorgeschrieben braucht.
Das Entfernen von Debug-Infos könnte man bei den Windows-Binaries natürlich
auch aktivieren, aber meiner Erfahrung nach bringt das nur wenige Kilobytes.
Eher könnte man diverse Runtime-Sicherheitsfeatures deaktivieren, was ebenso in kleinerem generiertem Code-Output mündet.
Natürlich ist mir auch klar, dass mit dynamisch gelinkten C-Runtimes die Binärpakete um ein paar hundert Kilobytes kleiner werden können, doch dafür verlieren sie die Fähigkeit auf allen Systemen ohne Installation der Runtime zu laufen … und dieses Opfer möchte ich nicht erbringen.
Wie auch immer. Mit ein paar Zeilen CMake
wurde fast 1 MB unnötiger Ballast
entfernt und die anderen hier nicht gelisteten Projekte profitieren
ebenfalls von der Magerkur.
Tja, wieder was gelernt … alles in allem also ein erfolgreicher Tag.