It is well-known that the gcc/ld options -ffunction-sections -fdata-sections -Wl,--gc-sections
may reduce binary sizes (see for example answers to Query on -ffunction-section & -fdata-sections options of gcc). It is also well-known that link-time optimization may reduce binary sizes and excution speed (see for example https://wiki.debian.org/LTO).
But what about the combination of section garbage collection and link-time optimization? Shouldn't link-time optimization render section garbage collection superfluous?
I have compiled Open CASCADE (https://www.opencascade.com/) with mingw-w64/g++ 9.2.0 with different compiler/linker options and just compared binary sizes. Open CASCADE is a set of multiple dlls. Most dlls are smallest if compiled just with -flto
.
Using -flto -ffunction-sections -fdata-sections -Wl,--gc-sections
instead, will most of the time slightly increase binary sizes. But there are some dlls that actually become slightly smaller when adding the garbage collection options.
Why is that the case? What could possibly be removed with garbage collection that could not be removed with bare link-time optimization? Is there a best practice which compile/link options to use for minimal binary sizes?
Remark: I have assumed that only -flto
will change execution times (hopefully make the binaries faster!) and link-time section garbage collection will most probably leave execution times as they are. I have not yet found time to measure that.
Edit: To make the described behaviour reproducible with real-life shared libraries, I have created a bash script to build Open CASCADE with two different parameter sets. The comments in the first lines describe how to adapt the script. I am using it here in an MSYS2 console on Windows.
#!/bin/bash
# This script requires at least CMake 3.13, otherwise the options "-S" and "-B" do not work as expected.
# Further it needs a mingw-w64 installation in the path.
# Path to the folder that has been downloaded and extracted from https://www.opencascade.com/sites/default/files/private/occt/OCC_7.3.0_release/opencascade-7.3.0.tgz
OCCT_PATH="/c/Libraries/opencascade-7.3.0"
# Path to the folder that has been downloaded and extracted from https://www.opencascade.com/sites/default/files/private/occt/3rdparty/freetype-2.6.3-mingw-64.7z
FREETYPE_PATH="/c/Libraries/freetype-2.6.3-mingw-64"
TEST_DIR="/c/Libraries/Test"
# Here the preparations are done... the script will (hopefully) do the rest.
if [ ! -d "$TEST_DIR" ]; then
mkdir "$TEST_DIR"
fi
# Build with link-time optimization
BUILD_DIR_LTO="BuildLto"
INSTALL_DIR_LTO="InstallLto"
CMAKE_C_FLAGS_LTO="-O2 -DNDEBUG -flto"
CMAKE_CXX_FLAGS_LTO="$CMAKE_C_FLAGS_LTO -std=gnu++17"
# According to https://stackoverflow.com/a/31688314/2492801 neither -flto nor an optimization flag is required with new gcc versions
CMAKE_SHARED_LINKER_FLAGS_LTO=""
cmake -DCMAKE_BUILD_TYPE="Release" -S "$OCCT_PATH" -B "$TEST_DIR/$BUILD_DIR_LTO" -G "MSYS Makefiles" -DINSTALL_DIR="$TEST_DIR/$INSTALL_DIR_LTO" -DCMAKE_CXX_FLAGS_RELEASE="$CMAKE_CXX_FLAGS_LTO" -DCMAKE_C_FLAGS_RELEASE="$CMAKE_C_FLAGS_LTO" -DCMAKE_SHARED_LINKER_FLAGS_RELEASE="$CMAKE_SHARED_LINKER_FLAGS_LTO" -DBUILD_MODULE_Draw=False -D3RDPARTY_FREETYPE_DIR="$FREETYPE_PATH"
cd "$TEST_DIR/$BUILD_DIR_LTO"
mingw32-make -j 24 install
# Build with link-time optimization and link-time section garbage collection
BUILD_DIR_GC="BuildLtoGc"
INSTALL_DIR_GC="InstallLtoGc"
CMAKE_C_FLAGS_GC="-O2 -DNDEBUG -flto -ffunction-sections -fdata-sections"
CMAKE_CXX_FLAGS_GC="$CMAKE_C_FLAGS_GC -std=gnu++17"
# According to https://stackoverflow.com/a/31688314/2492801 neither -flto nor an optimization flag is required with new gcc versions
CMAKE_SHARED_LINKER_FLAGS_GC="-Wl,--gc-sections"
cmake -DCMAKE_BUILD_TYPE="Release" -S "$OCCT_PATH" -B "$TEST_DIR/$BUILD_DIR_GC" -G "MSYS Makefiles" -DINSTALL_DIR="$TEST_DIR/$INSTALL_DIR_GC" -DCMAKE_CXX_FLAGS_RELEASE="$CMAKE_CXX_FLAGS_GC" -DCMAKE_C_FLAGS_RELEASE="$CMAKE_C_FLAGS_GC" -DCMAKE_SHARED_LINKER_FLAGS_RELEASE="$CMAKE_SHARED_LINKER_FLAGS_GC" -DBUILD_MODULE_Draw=False -D3RDPARTY_FREETYPE_DIR="$FREETYPE_PATH"
cd "$TEST_DIR/$BUILD_DIR_GC"
mingw32-make -j 24 install
The file Test/BuildLto/win64/gcc/bin/libTKGeomAlgo.dll
has a size of 4378624 bytes, but Test/BuildLtoGc/win64/gcc/bin/libTKGeomAlgo.dll
has a size of 4377600 bytes. So additional link-time garbage collection reduces the file size even more than only link-time optimization. Why???
Remark: As stated before this is an exception. More often link-time garbage collection makes the resulting shared libraries larger.
Update: I have tried the experiment also with gcc 10.2 and gcc 11.2 on Windows (mingw-w64). The observation is the same: link-time garbage collection makes the resulting shared libraries on average larger.