LTO
Link Time Optimization (LTO) is the term for a class of optimizations done during linking. The linker is aware of all the translation units (TUs) and can optimize more compared to what conventional optimization passes by a compiler individually can do. The most important optimizations done are inlining and code locality improvements. Inlining is the driver of optimizations in general and facilitates other compiler passes. This can improve system performance at the cost of longer compile time.
This article will show the user how to enable LTO without the need for third party overlays and using recommended tested CFLAGS for the best performance and system stability (with either GCC or LLVM-based toolchains).
Before Beginning
LTO has a risk-to-reward ratio to work out before a user decides to switch. If the system has a 4 core CPU with 4GB of RAM as an example, then the user will very likely spend much more time waiting for a package to compile than they could ever hope to recover in optimization runtime speed, obligatory XKCD.
Benefits of LTO
- LTO can give double-digit performance boosts for many programs.
- Binaries can become much smaller.
- Startup time may be improved, especially for large C++ applications.
- Better diagnostics (warnings) for problematic code (GCC).
- Can lower RAM usage per program making it very useful for limited memory systems.[1]
Downsides of LTO
- Can increase compile time by a factor of 2 to 3.
- Uses more RAM during compiling.
- Not all programs become faster or smaller.
- There is a chance of finding build-time or runtime bugs while using it.
- Must be prepared to try without it if something is acting odd.
- Reporting problems like compiler crashes become much harder than without LTO, because the compiler no longer works on one object file at-a-time.
Enabling LTO System-wide
GCC Systems
Enable LTO
To enable LTO package building on GCC systems, edit /etc/portage/make.conf then add LTO to the USE line and -flto
to the COMMON_FLAGS.
/etc/portage/make.conf
make.conf example# These warnings indicate likely runtime problems with LTO, so promote them
# to errors. If a package fails to build with these, LTO should not be used there.
WARNING_FLAGS="-Werror=odr -Werror=lto-type-mismatch -Werror=strict-aliasing"
COMMON_FLAGS="-O2 -pipe -march=native -flto ${WARNING_FLAGS}"
CFLAGS="${COMMON_FLAGS}"
CXXFLAGS="${COMMON_FLAGS}"
FCFLAGS="${COMMON_FLAGS}"
FFLAGS="${COMMON_FLAGS}"
LDFLAGS="${LDFLAGS} ${WARNING_FLAGS}"
USE="lto"
With this, LTO is now enabled on the system for all future emerges.
With GCC you can also pass the number of processes to use for linking to the -flto
parameter using -flto=NUMBER-OF-PROCESSES
which will speed it up. Using -flto
on its own is normally all that is needed though as it automatically picks the best number of processes to use for the system. The default single-threaded link optimization was changed to multi-threaded in 2019.[2]
LLVM Systems
Enable LTO
All that is needed is to -flto=thin
to COMMON_FLAGS and lto
to USE:
/etc/portage/make.conf
make.conf example# These warnings indicate likely runtime problems with LTO, so promote them
# to errors. If a package fails to build with these, LTO should not be used there.
#
# NOTE: As of 2025-03-10, clang does not actually implement diagnostics for any
# of this. If you want to check for issues, use GCC. We list these flags
# so that in the future, if clang starts respecting them, they are automatically
# used by existing make.conf files.
#
# As of 2024-11-11, Clang has a pull request for -Wstrict-aliasing as a real
# warning (noop for now):
# https://github.com/llvm/llvm-project/pull/74155
#
# As of 2024-11-11, Clang lacks -Wlto-type-mismatch entirely:
# https://github.com/llvm/llvm-project/issues/56487
#
# As of 2025-03-10, clang has -Wodr but doesn't use it for much:
# https://github.com/llvm/llvm-project/issues/34914
WARNING_FLAGS="-Werror=odr -Werror=strict-aliasing"
COMMON_FLAGS="-O2 -pipe -march=native -flto=thin ${WARNING_FLAGS}"
CFLAGS="${COMMON_FLAGS}"
CXXFLAGS="${COMMON_FLAGS}"
FCFLAGS="${COMMON_FLAGS}"
FFLAGS="${COMMON_FLAGS}"
LDFLAGS="${LDFLAGS} ${WARNING_FLAGS}"
USE="lto"
Clang added the multithreaded -flto=thin
flag in 2016.[3]
Rust on LLVM systems
To compile Rust programs with LTO then, enable it in both COMMON_FLAGS and RUSTFLAGS.
/etc/portage/make.conf
make.conf exampleRUSTFLAGS="${RUSTFLAGS} -Clinker-plugin-lto"
Rust on GCC systems
As of 2024-11-09, Rust docs say:
The -C linker-plugin-lto flag allows for deferring the LTO optimization to the actual linking step, which in turn allows for performing interprocedural optimizations across programming language boundaries if all the object files being linked were created by LLVM based toolchains. The prime example here would be linking Rust code together with Clang-compiled C/C++ code.
[...]
C/C++ code as a dependency in Rust
RUSTFLAGS="-Clinker-plugin-lto -Clinker=clang -Clink-arg=-fuse-ld=lld"
Therefore, to have LTO for Rust code on GCC systems, one must build packages selectively with Clang (for now). Create /etc/portage/env/llvm-lto.conf with these contents:
/etc/portage/env/llvm-lto.conf
llvm-lto.conf example# These warnings indicate likely runtime problems with LTO, so promote them
# to errors. If a package fails to build with these, LTO should not be used there.
#
# As of 2024-11-11, Clang has a pull request for -Wstrict-aliasing as a real
# warning (noop for now):
# https://github.com/llvm/llvm-project/pull/74155
#
# As of 2024-11-11, Clang lacks -Wlto-type-mismatch:
# https://github.com/llvm/llvm-project/issues/56487
WARNING_FLAGS="-Werror=odr -Werror=strict-aliasing"
COMMON_FLAGS="-march=native -O2 -flto=thin -pipe ${WARNING_FLAGS}"
CFLAGS="${COMMON_FLAGS}"
CXXFLAGS="${COMMON_FLAGS}"
FCFLAGS="${COMMON_FLAGS}"
FFLAGS="${COMMON_FLAGS}"
RUSTFLAGS="-C target-cpu=native -C strip=debuginfo -C opt-level=3 \
-Clinker=clang -Clinker-plugin-lto -Clink-arg=-fuse-ld=lld"
LDFLAGS="${LDFLAGS} ${WARNING_FLAGS} -fuse-ld=lld"
CC="clang"
CXX="clang++"
CPP="clang-cpp"
AR="llvm-ar"
NM="llvm-nm"
RANLIB="llvm-ranlib"
USE="lto"
Then, in /etc/portage/package.env, specify llvm-lto.conf after each package where Rust LTO is desired.
USE Flags
On both GCC and LLVM systems, some packages require special fixes to work with LTO and this is enabled by setting globally the lto USE flag.
/etc/portage/make.conf
USE flag exampleUSE="lto"
Exceptions (USE="lto")
There are some complicated packages, such as Mozilla Firefox and its derivatives (Thunderbird, SpiderMonkey) that require lto to manage a working dependency chain so the project can be built. But the goal is to get rid of the lto use flag where possible, as much as possible.
Troubleshooting, Fixing and Reporting LTO Issues
While LTO works without issue for most users thanks to the hard work of everyone in the community in Gentoo and beyond, there are still some packages that a user may run across that have been missed.
This section will show how to fix the issue for the their system and also report the issue (if needed) so it can be fixed for everyone to help make Gentoo better for everyone in the future.
Stable Systems
As of 2023-05-15, LTO fixes have mostly trickled down from testing to stable (e.g. ~amd64 to amd64). However, there may be exceptions, so before reporting, try the latest version of the package in the testing branch to see if it's already fixed and save yourself and the developers time and effort.
In this example, app-editors/emacs is used to show the steps. However, please be aware this may already be fixed at the time of reading, so should only be viewed as an example of the troubleshooting steps LTO users should follow.
root #
emerge --ask app-editors/emacs
In this example, on a stable amd64 system, Portage would compile version 28.2-r6. On the first sign of a build issues, check packages.gentoo.org if there is a version in testing. In this case, one would find that there is a version 28.3_rc1-r2 available.
In this case, the next step would be to temporarily accept the keyword for just that version so Portage only uses the testing version until the stable package version catches up to the fix.
To only accept the keyword for one version, then an =
is required before the package type and then the version number after:
/etc/portage/package.accept_keywords/emacs
Keywording Example=app-editors/emacs-28.3_rc1-r2
Then try to emerge the updated version:
root #
emerge --ask app-editors/emacs
If the package compiles fine now, then no reporting is necessary. If, however, it still fails, then continue to the next step.
Latest Version Issues
Existing Reports Procedure
First check if a bug already exists for the problem at bugs.gentoo.org. Using the search tool, looking up "emacs lto" would show an already reported bug as bug #854360.
Next, compare the reported version to the version tested on the system. If the versions are the same, then no action is needed, however, if the tested version is newer, then adding a comment that the problem still exists in the testing version should be added to help save time for the person working on the bug.
New Bugs Procedure
In the case of no bug being open after searching, opening a new report is one of the most helpful things a Gentoo user can do to help highlight issues.
Do remember there is no shame if a mistake is made so there is no need for a user to feel nervous as you will be guided on what extra information is needed if required.
An imperfect bug report is always preferred over a perfect report that never materializes.
Information Required
A good title helps, for example "app-editors/emacs-28.3_rc1-r2: fails to compile with LTO"
A short description of the issue and what has been tried so far.
Steps to reproduce, preferably from a fresh stage3 install:
- Install stage3 <tarball name used>
- Enable lto in /etc/portage/make.conf via flags, per above instructions
- Add
app-editors/emacs-28.3_rc1-r2
to package.accept_keywords - emerge -va app-editors/emacs
Attach the build.log to the bug report from the location portage gives.
Add the output of emerge --info to a section comment.
This may sound confusing the first time, however it's very easy after the first time, and can be fun to watch a reported bug get fixed in real time.
Disable LTO per Package
After reporting, it's still possible to workaround the issue until a fix is committed by disabling LTO using package.env.
Create nolto.conf
package.env configuration is a Portage feature that allows a user to set CFLAGS and other options on a per-package basis.
Create /etc/portage/env/nolto.conf with the following:
/etc/portage/env/nolto.conf
nolto.conf example# Env setup to disable LTO and related warnings for problematic builds
DISABLE_LTO="-Wno-error=odr -Wno-error=lto-type-mismatch -Wno-error=strict-aliasing -fno-lto"
CFLAGS="${CFLAGS} ${DISABLE_LTO}"
CXXFLAGS="${CXXFLAGS} ${DISABLE_LTO}"
FCFLAGS="${FCFLAGS} ${DISABLE_LTO}"
FFLAGS="${FFLAGS} ${DISABLE_LTO}"
This is the configuration that Portage will use for the packages defined in the next step.
Create package.env/noltobuild
Suppose further that emacs is a problematic package, then /etc/portage/package.env/noltobuild would look like:
/etc/portage/package.env/noltobuild
noltobuild config exampleapp-editors/emacs nolto.conf
Now compiling app-editors/emacs
on the user's system would no longer build with LTO and the compilation issue found at the start would be fixed as a workaround until a permanent fix is found in Gentoo.
See Also
- Clang — a C/C++/Objective-C/C++, CUDA, and RenderScript language front-end for the LLVM project
- LTO optimization of GCC
- Project:Toolchain/LTO — details the Toolchain project's position on Link Time Optimization.
References
- ↑ Teresa Johnson, Mehdi Amini, Xinliang David Li. ThinLTO - Building C++ Applications with Scalable Whole Program Optimization, September 27, 2017. Retrieved on May 5, 2024
- ↑ Liska, Martin. Deduce automatically number of cores for -flto option., July 30, 2019. Retrieved on November 10, 2024.
- ↑ Wennborg, Hans. LLVM 3.9 Release, September 2, 2016. Retrieved on November 10, 2024.