include/linux/kallsyms.h | 13 +++ kernel/kallsyms.c | 210 ++++++++++++++++++++++++++++-------- lib/Kconfig.debug | 10 ++ lib/Makefile | 1 + lib/test_kallsyms_perf.c | 269 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 457 insertions(+), 46 deletions(-)
kallsyms_lookup_names() resolves symbol names to addresses using a
17-step binary search over kallsyms_names[] (~184k symbols on x86_64).
At each step of the search, two bottlenecks compound to create
substantial lookup latency:
0. Marker scanning: get_symbol_offset() scans sequentially from the
nearest 256-symbol marker, decoding an average of ~128 ULEB128 record
headers per probe (~2,176 header decodes per lookup).
1. Redundant string expansion: kallsyms_expand_symbol() decompresses
the entire candidate symbol into a 512-byte stack buffer (namebuf)
before calling strcmp(), even though ~94% of binary search probes
mismatch on the first 1-2 characters.
Together, these bottlenecks impose a ~3.8 us latency penalty per hit and
~3.6 us per miss.
This 3-patch series eliminates both overheads while keeping the
symbol table strictly in sequential address order and adding 0 bytes to
.rodata:
0. Patch 1 adds lib/test_kallsyms_perf.ko, a microbenchmark module to
measure unindexed vs dynamic indexed name searches, address
resolution, and table iteration latency, with built-in correctness
validation and a sysfs trigger.
1. Patch 2 introduces a dynamic u32 lookup index bracketed by
kallsyms_lookup_batch_start() and kallsyms_lookup_batch_end().
It allocates ~736 KiB in transient RAM via kvmalloc_array() only
while bulk workloads (BPF attach, module loading) run, resolves
each probe in O(1) with 0 hops, and leaves .rodata bloat at exactly
0 bytes while retaining kallsyms_markers[] as fallback.
2. Patch 3 introduces kallsyms_strcmp_symbol() to compare ASCII queries
against compressed tokens incrementally on the fly, bailing out on
the first mismatched character without expanding subsequent tokens.
This drops the 512-byte namebuf buffer from the kernel stack.
Live Microbenchmark Results (via test_kallsyms_perf, 100k iters):
Metric Baseline Patched Speedup
-----------------------------------------------------------------
Name Search Hit 3,811 ns 246 ns 15.5x
Name Search Miss 3,625 ns 196 ns 18.5x
sprint_symbol 412 ns 412 ns parity
sprint_symbol_no_offset 300 ns 300 ns parity
Table Full Walk 13,626 us 13,626 us parity
Address-to-name resolution (sprint_symbol) and sequential table walks
(/proc/kallsyms) remain completely unaffected, maintaining full L1/L2
hardware prefetching.
Memory footprint: +0 KiB .rodata added to kernel image. Transient RAM
is ~736 KiB (184k * 4 bytes) allocated only during active batch
lookup sessions.
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
Changes in v2:
- Replaced static build-time 3-byte offset table with a dynamic u32
index bracketed by kallsyms_lookup_batch_start() and
kallsyms_lookup_batch_end().
- Dropped .rodata image footprint addition from +573 KiB to 0 KiB,
addressing Kees Cook's memory footprint objection.
- Native u32 loads in transient RAM eliminate 24-bit big-endian shifts
and unaligned loads, addressing David Laight's endianness critique.
- Direct O(1) table indexing provides 0 hops for all symbol lookups
without remainder logic or odd/even branching.
- Restored scripts/kallsyms.c and kernel/kallsyms_internal.h to pristine
state, leaving legacy kallsyms_markers[] as safety fallback.
- Rebased out Lorenzo Stoakes' kbuild series; this series is now
completely decoupled and applies cleanly directly onto mainline.
- Updated test_kallsyms_perf to benchmark unindexed marker scans and
dynamic index side by side in a single run.
- Link to v1: https://lore.kernel.org/r/20260919-ksyms-tune-v1-0-d85c97da1a32@gmail.com
---
Jim Cromie (3):
kallsyms: Add test_kallsyms_perf module to benchmark lookup latency
kallsyms: Add dynamic lookup index for batch resolution
kallsyms: Match compressed tokens on the fly during binary search
include/linux/kallsyms.h | 13 +++
kernel/kallsyms.c | 210 ++++++++++++++++++++++++++++--------
lib/Kconfig.debug | 10 ++
lib/Makefile | 1 +
lib/test_kallsyms_perf.c | 269 +++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 457 insertions(+), 46 deletions(-)
---
base-commit: 93f51579e7df248780214094418f205253383cc5
change-id: 20260919-ksyms-tune-e22a42d8a31a
Best regards,
--
Jim Cromie <jim.cromie@gmail.com>
On Tue, 22 Sep 2026 01:19:18 -0600 Jim Cromie <jim.cromie@gmail.com> wrote: > kallsyms_lookup_names() resolves symbol names to addresses using a > 17-step binary search over kallsyms_names[] (~184k symbols on x86_64). > At each step of the search, two bottlenecks compound to create > substantial lookup latency: > > 0. Marker scanning: get_symbol_offset() scans sequentially from the > nearest 256-symbol marker, decoding an average of ~128 ULEB128 record > headers per probe (~2,176 header decodes per lookup). > > 1. Redundant string expansion: kallsyms_expand_symbol() decompresses > the entire candidate symbol into a 512-byte stack buffer (namebuf) > before calling strcmp(), even though ~94% of binary search probes > mismatch on the first 1-2 characters. > > Together, these bottlenecks impose a ~3.8 us latency penalty per hit and > ~3.6 us per miss. How much does just doing change 1 give you? Might be worth putting that patch first. If you do the binary chop using only 256 aligned symbols it won't add any more stages but means you don't need to scan until the 256 symbol block has been identified. At that point there are two options: B: A linear scan - average 128 compare per lookup. A: Generate a table of the offsets for the next 128 symbols and do a binary scan (only read the second 128 if in the second half). The linear scan may not be too bad. You can get the first data byte while sorting out the length and then to an initial check that the first few characters match before adding in the complexity of the loop along the compressed data. David
© 2016 - 2026 Red Hat, Inc.