ThinLTO: Faster Links Than Full LTO, Better Optimization Than -O2
Use Clang ThinLTO to get most link-time optimization benefits without full LTO's link-time and memory costs. Learn when to choose it and how to verify results.
07 Aug 2025, 13:13 UTC

The build-speed wall at full LTO
You enable -flto hoping for faster binaries, and the link step consumes all available memory for twenty minutes. On a large C or C++ codebase, full link-time optimization can turn a ten-minute build into an hour-long one. Turn LTO off, and functions in separate translation units miss inlining and specialization opportunities. The practical question is whether you can get most of the optimization benefit without paying the full link-time tax.
ThinLTO, exposed by Clang as -flto=thin, is designed for exactly that trade-off. It performs per-function summary analysis and imports only the functions likely to profit from cross-module optimization, rather than merging every bitcode module into one whole-program image.
What ThinLTO changes in the build
With full LTO (-flto), the compiler emits LLVM bitcode for every object file, and the linker reads all of it to perform whole-program analysis. ThinLTO also emits bitcode, but it attaches a compact summary to each object. During linking, the toolchain reads those summaries, decides which functions to import across module boundaries, and runs localized optimization passes on the imported code.
The result is that inlining and specialization can still cross translation-unit boundaries, but the linker does not reoptimize the entire program. Changed files usually require reimporting only affected summaries, which helps incremental builds compared with full LTO.
Worked example: a ThinLTO build with Clang and LLD
Run these commands in a terminal at your project root. You need write access to the build directory and a Clang/LLVM toolchain that supports ThinLTO; behavior varies by version, so re-check after toolchain upgrades. Replace /path/to/project with your actual source tree.
cd /path/to/project
rm -rf build
mkdir build
cd build
CXX=clang++ \
CC=clang \
CXXFLAGS="-O2 -flto=thin" \
CFLAGS="-O2 -flto=thin" \
LDFLAGS="-fuse-ld=lld -flto=thin" \
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
The -fuse-ld=lld flag tells the build to use LLVM's linker, which is generally faster than the system linker for LTO workloads. The -flto=thin flag appears in both compile and link flags because ThinLTO needs bitcode at compile time and summary-driven importing at link time.
Expected checks: the link step should complete on projects where full LTO previously exhausted memory, and you may see ThinLTO-related messages in the link output. Object files will be larger than non-LTO objects because they carry embedded summaries. You can check for LLVM bitcode sections with:
llvm-readobj -sections build/your_object.o | grep -i bitcode
If the command returns no output, that object was not compiled with LLVM bitcode and will be excluded from ThinLTO optimization.
Trade-offs and limitations
- Object size: ThinLTO objects are larger because each one embeds a summary section.
- Debugging: Cross-module inlining can make stack traces and source locations harder to follow.
- Mixed languages: Assembly or precompiled objects without LLVM bitcode are excluded unless they carry bitcode.
- Version dependence: Import decisions and optimization results can change between Clang/LLVM releases.
- Link cost: ThinLTO is cheaper than full LTO, but link time and memory still grow with program size.
Do not assume ThinLTO matches full LTO runtime performance. The only reliable way to know is to build your actual workload both ways and compare.
How to verify the result
- Build the same project with
-O2,-flto=thin, and-fltousing a recent Clang and LLD. - Measure link time, peak linker memory, binary size, and your target benchmark runtime for each build.
- Inspect link output for ThinLTO import messages and use
llvm-nmorreadelfto confirm symbol resolution differs between builds.
If ThinLTO gives you most of the runtime gain at a fraction of the link cost, it is usually the right default. Reserve full LTO for release builds where the benchmark justifies the extra link time.
Rollback
Because changing compiler flags alters build artifacts, roll back by removing the build directory and rebuilding without ThinLTO:
rm -rf build
CXX=clang++ CC=clang cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
Bottom line
Start with -flto=thin and LLD for large C and C++ projects. Measure your link times and benchmark numbers before moving to full LTO, and treat LLVM version upgrades as a reason to re-check import behavior rather than assume it stays the same.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.