[PATCH 00/23] kbuild: significantly speed up kernel builds
From: "Lorenzo Stoakes (ARM)" <ljs@kernel.org>
Date: 2026-09-08 20:55:33
Also in:
linux-doc, linux-efi, linux-kbuild, linux-riscv, lkml, llvm, rust-for-linux
A typical kernel build consists of a frustratingly large amount of time
spent stuck in single-threaded bottlenecks.
It turns out that there's a lot we can do about this and doing so
significantly impacts kernel build times.
This series makes allmodconfig builds up to 36% faster, incremental builds
up to ~70% faster, and noop builds up to ~90% faster.
Builds are faster across the board on every device I tested.
Machines used for perf testing:
* Threadripper - x86, AMD Threadripper 9980X, 64 cores, 128 threads
* EPYC - x86, 2 socket EPYC 9754, 256 cores, 512 threads
* M2 - arm64, 2022 M2 macbook pro, 8 cores, 8 threads
(4 perf, 4 efficiency)
Cutting to the chase:
== allmodconfig FULL build ==
before after delta
----------------------------------
Threadripper, gcc 345.3s 275.2s -70.1s (-20%)
Threadripper, clang 345.5s 265.7s -79.8s (-23%)
EPYC, gcc 188.6s 121.1s -67.5s (-36%)
EPYC, clang 261.7s 184.6s -77.1s (-29%)
== allmodconfig INCREMENTAL build ==
before after delta
----------------------------------
Threadripper, gcc 46.4s 15.3s -31.1s (-67%)
Threadripper, clang 44.4s 15.2s -29.1s (-66%)
EPYC, gcc 82.0s 24.3s -57.7s (-70%)
EPYC, clang 77.6s 24.3s -53.3s (-69%)
== allmodconfig NO-OP build ==
before after delta
----------------------------------
Threadripper, gcc 12.97s 1.18s -11.79s (-91%)
Threadripper, clang 13.84s 1.64s -12.20s (-88%)
EPYC, gcc 25.92s 1.52s -24.40s (-94%)
EPYC, clang 27.58s 2.17s -25.41s (-92%)
== defconfig FULL build ==
before after delta
----------------------------------
Threadripper, gcc 35.3s 28.6s -6.7s (-19%)
Threadripper, clang 33.5s 26.1s -7.4s (-22%)
EPYC, gcc 31.2s 20.6s -10.6s (-34%)
EPYC, clang 38.6s 32.2s -6.4s (-17%)
M2, gcc 564.0s 512.4s -51.6s (-9%)
M2, clang 616.1s 569.4s -46.7s (-8%)
== defconfig INCREMENTAL build ==
before after delta
----------------------------------
Threadripper, gcc 11.6s 5.5s -6.1s (-53%)
Threadripper, clang 11.6s 5.0s -6.6s (-57%)
EPYC, gcc 19.3s 9.2s -10.1s (-52%)
EPYC, clang 20.1s 8.6s -11.5s (-57%)
M2, gcc 18.6s 9.9s -8.7s (-47%)
M2, clang 18.6s 8.2s -10.4s (-56%)
== defconfig NO-OP build ==
before after delta
----------------------------------
Threadripper, gcc 0.96s 0.47s -0.49s (-51%)
Threadripper, clang 1.21s 0.55s -0.66s (-55%)
EPYC, gcc 1.47s 0.66s -0.81s (-55%)
EPYC, clang 1.87s 0.80s -1.07s (-57%)
M2, gcc 5.59s 1.69s -3.90s (-70%)
M2, clang 6.62s 1.74s -4.88s (-74%)
Further performance numbers are provided for each commit giving a sense of
what each contributes to the final result.
== Testing ==
Beyond x86, allmodconfig was built with the series for arm64, arm, riscv,
powerpc64, s390 and loongarch, and for arm64, arm, s390 and loongarch the
System.map is identical to what the previous shell mksysmap produces for
the same vmlinux.
Also tested were parisc64 and m68k build as far as mainline lets them
(a driver's static assertion and an undefined-symbol check on parisc, gcc
16 internal compiler errors on m68k, none of it from this series).
Kernels for x86, arm64, arm, riscv, loongarch, powerpc64, s390, m68k and
parisc64 all boot under qemu, every text symbol of System.map is in
/proc/kallsyms at the relocated address, and a module loads and unloads.
The s390, arm and loongarch kernels also pass the kallsyms selftest.
An x86 kernel with CONFIG_MODVERSIONS, CONFIG_EXTENDED_MODVERSIONS and
CONFIG_MODULE_SRCVERSION_ALL boots, loads and unloads modules. External
modules build against both in-tree and O= builds, and every commit builds
on x86 defconfig.
Build times are the best of several runs, no unexpected errors or
warnings were seen.
While some aspects of the build process have been changed, all tooling
should function identically to before.
== LLM usage ==
An LLM was used to first determine where the bottlenecks were then to
figure out how to improve them.
It generated a lot of code, much of it hideous.
I extensively audited and rewrote a lot of it, and heavily edited commit
messages, the cover letter and comments.x
The LLM has also orchestrated build runs, testing, debugging and analysis.
I have manually checked for correctness in both build and running kernels
generated with this series applied.
Performance improvements were also verified manually.
Since an LLM was used extensively, each commit carries an Assisted-by tag.
== What was changed? ==
Fundamentally the series improves build times by parallelising
single-threaded tasks as much as possible and improving the efficiency of
code used in the build process.
kbuild, kallsyms, modpost, objtool, mksysmap and the rust build system were
all updated as part of this change.
Nothing too controversial was included. There are further improvements that
could be made, but they would either by very invasive (large scale C header
changes) or generate diminishing returns.
== Patches ==
mksysmap (1, 2): A couple of bugfixes the rest of the series relies
upon.
kallsyms (3, 4): Some efficiency improvements through use of a cache.
kbuild (5, 6): Don't sort nm output unnecessarily, do not include
relocations in the kallsyms trial links.
elf-parse (7): Section flags, symbol binding and a read-only mapping,
for the next patch.
kallsyms (8): Don't use nm, read the ELF symbol table directly.
kbuild (9): Make .modinfo an INFO section.
kbuild (10, 11): Implement a cache to track objects, check dependency
timestamps more efficiently.
kbuild (12): Probe compiler, linker flags once at top of build.
modpost (13, 14): Hash module sources a file at a time not a byte at a
time, also improve performance through use of a
cache.
modules (15, 16): Emit module descriptors as assembly (*.mod.S) rather
than C (*.mod.c), reducing CPU seconds taken by 10x to
perform the task. Also shard module finalisation
rather than running 10's of thousands of tiny runs.
modpost (17): Compute srcversions in parallel using a thread pool.
objtool (18, 19): Avoid polluting the reloc hash with millions of DWARF
relocations. Remember dead-ends and decode large
objects over multiple threads. Size instruction hash
to the code rather than hard-code it.
rust (20-22): Parallelise the frontend, set correct dependencies and
build crates in parallel with C code, retaining the
requirement that rust/ crates are built first.
kbuild (23): Default to using a parallel implementation of gzip
(pigz) if available on the system.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
Lorenzo Stoakes (ARM) (23):
scripts/mksysmap: drop the MODULE_INFO() symbols from kallsyms
scripts/mksysmap: fix escape of '$' in the __pi_ pattern
kallsyms: index symbols by token to speed up table compression
kallsyms: output binary data to speed output and kallsyms assembly
kbuild: do not sort nm output where the order is irrelevant
kbuild: only emit vmlinux relocations when required
elf-parse: add section flags, symbol binding and a read-only mapping
kallsyms: reimplement mksysmap in C
kbuild: do not allocate .modinfo in vmlinux
kbuild: cache list, composite object state per object
kbuild: implement and use depcheck to check dependency timestamps
kbuild: avoid re-running compiler and linker probes
modpost: hash module source per-file, not per-byte
modpost: cache section relocation mismatch state
modpost: emit module descriptors as assembly
kbuild: batch module finalisation
modpost: perform srcversion hashing in parallel
objtool: cache relocations and function dead end state, do less work
objtool: decode instructions and resolve branch targets in parallel
kbuild: rust: parallelise rustc front end
rust: make exports.o depend on the headers generated for it
kbuild: build rust crates in parallel with the rest of the build
kbuild: use pigz for gzip compression if available
.gitignore | 1 +
Documentation/kbuild/kbuild.rst | 13 +
Documentation/kbuild/reproducible-builds.rst | 16 +
Kbuild | 5 +
Makefile | 49 +-
arch/arm64/kernel/pi/Makefile | 2 +-
arch/riscv/kernel/pi/Makefile | 2 +-
arch/x86/Kconfig | 17 +
arch/x86/Makefile | 12 +-
arch/x86/boot/Makefile | 2 +-
arch/x86/boot/compressed/Makefile | 2 +-
drivers/firmware/efi/libstub/Makefile | 2 +-
include/asm-generic/vmlinux.lds.h | 2 +-
include/linux/vermagic.h | 2 +-
init/Kconfig | 84 +++
rust/Makefile | 5 +
scripts/Makefile | 4 +-
scripts/Makefile.build | 27 +-
scripts/Makefile.lib | 7 +-
scripts/Makefile.modfinal | 36 +-
scripts/Makefile.modpost | 2 +-
scripts/Makefile.vmlinux | 27 +-
scripts/Makefile.warn | 28 +-
scripts/basic/.gitignore | 1 +
scripts/basic/Makefile | 2 +-
scripts/basic/depcheck.c | 442 +++++++++++++++
scripts/check-function-names.sh | 3 +-
scripts/elf-parse.c | 48 +-
scripts/elf-parse.h | 19 +
scripts/kallsyms-sysmap.c | 269 ++++++++++
scripts/kallsyms.c | 387 ++++++++++----
scripts/kallsyms.h | 44 ++
scripts/link-vmlinux.sh | 25 +-
scripts/mksysmap | 94 ----
scripts/mod/.gitignore | 1 +
scripts/mod/Makefile | 9 +
scripts/mod/modpost.c | 757 ++++++++++++++++++++------
scripts/mod/modpost.h | 2 +
scripts/mod/module-offsets.c | 35 ++
scripts/mod/sumversion.c | 57 +-
scripts/tags.sh | 5 +-
tools/objtool/Makefile | 2 +-
tools/objtool/check.c | 767 ++++++++++++++++++++-------
tools/objtool/elf.c | 242 ++++++++-
tools/objtool/include/objtool/elf.h | 4 +
tools/objtool/include/objtool/objtool.h | 3 +-
tools/objtool/objtool.c | 1 -
47 files changed, 2884 insertions(+), 682 deletions(-)
---
base-commit: 28924df2a08f440c73991b83028032c901de2ae4
change-id: 20260904-build-speedup-25e11a00b3d0
Best regards,
--
Lorenzo Stoakes (ARM) [off-list ref]