mold links Chromium in two seconds. Your build chain is still waiting on the linker.
Published by RodHat

Linking is the part of the build chain that everyone accepts as slow. Compiler phase: parallel, cached, incremental. Build system: ccache, sccache, distributed builds, dependency graphs tight enough to only recompile what changed. Linker: wait.
GNU ld has been shipping since 1990. Gold was written in 2008 as a faster ELF-only replacement. lld came out of the LLVM project and is now the system linker on FreeBSD and the default for Clang-based builds on most modern systems. None of them were designed to use more than a couple of your cores for the linking step.
mold was.
Why linkers serialize
A linker takes a pile of object files, resolves every undefined symbol reference against the pool of defined symbols across all inputs, applies relocations to patch addresses in place, merges sections from each input into output sections, and writes the binary.
The reference design does this in passes. Read archive members in link-order until no undefined symbols remain. Process each object file against the accumulated symbol table. Compute output addresses once symbol resolution is complete. Merge sections sequentially. Write the output. Every step waits for the previous step.
The bottleneck is not arithmetic. It is serialized memory accesses to a global hash table and sequential section copying. On a 16-core workstation, the compiler phase saturates all 16 cores. The link phase uses one.
Nobody fixed this for decades because for most codebases the link step was not the problem. When a full rebuild took twenty minutes, a 45-second link was noise. When ccache and incremental compilation pushed a partial rebuild to three seconds, that same 30-second link became the entire cycle time.
What mold does differently
Rui Ueyama wrote lld. He then wrote mold because lld still serialized in the wrong places. The design notes are in the repository and are worth reading if you care about systems programming in the traditional sense of the phrase.
mold reads all input files in parallel at startup. Symbol table construction uses a
concurrent hash map with fine-grained per-bucket locking so multiple threads can insert
symbols simultaneously. After a single parallel pass over all inputs, mold has the complete
symbol graph. Address assignment is a brief serial phase. mold opens the output file at its
final size upfront, then each thread writes its assigned sections to non-overlapping byte
ranges via pwrite(2), simultaneously. The OS handles the IO scheduling; mold hands it
parallel work.
The technical basis for this is not obscure: most of what a linker does is embarrassingly parallel. The symbols in one object file do not depend on another object file’s symbols until the resolution pass. Sections from different inputs are independent bytes that can be copied concurrently. The serial core of the problem is small. The reason it was not done earlier is that the serial implementation was fast enough, until it wasn’t.
The numbers: linking Chromium on an 8-core workstation takes roughly 2 seconds with mold. The same link runs around 6 seconds with lld and over a minute with GNU gold. For a large C++ project with many translation units, mold is typically 2-3x faster than lld and 5-10x faster than gold.
When to switch
For C and C++ development builds on Linux where the link step is your longest phase, the switch is one flag:
# gcc/clang
-fuse-ld=mold
# CMake
cmake -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=mold" ...
That is the whole change. No new build system, no new toolchain, no migration.
When not to switch:
Kernel builds: mold does not support all linker script syntax the Linux kernel uses. Stay on lld or GNU ld.
LTO: mold uses the LLVM LTO plugin interface and works with Clang thin-LTO. GCC fat-LTO support is version-dependent. Check the release notes before committing your CI pipeline to it.
FreeBSD: lld is the system default, is already well-tuned, and getting lld’s link times is not generally the complaint people have about FreeBSD build times. mold is in ports. Use it if you have a reason.
Embedded targets with exotic architectures: mold supports x86-64, aarch64, RISC-V, ARM32, PowerPC, s390x, and LoongArch. If your target is not in that list, you are on lld or GNU ld.
Why this took thirty years
Build toolchains optimize in sequence. You fix the slowest thing, the next thing becomes the slowest thing, repeat. The linker became the bottleneck only after the compile phase got fast enough to expose it. That is the right order. It is also why nobody should be proud that the parallel linker arrived in 2021 and not in 1999.
The Linux 7.4 kbuild post from last week covered profiling work going into the kernel build system itself. Same lesson: measure the actual bottleneck, not the one that was obviously slow five years ago. The GCC 16 post from August is relevant here too, since faster compiler output rates make the link step relatively more expensive.
If your C or C++ builds are link-bound and you are not using mold, you are waiting for
a problem that was solved. The binary is at mold --version. The benchmark is -fuse-ld=mold.
Run it once and read the time output. The argument is over after that.
Sources
- mold source code and documentation (Rui Ueyama / mold project)
- mold design notes (Rui Ueyama)