[PATCH v2 02/18] perf demangle: Add fast-path prefix checks for Rust and Java demanglers
From: Ian Rogers
Date: Tue Oct 27 2026 - 19:17:22 EST
During DSO symbol loading in dso__load_sym_internal(), dso__demangle_sym()
is called for every ELF symbol. Both rust_demangle_demangle() and
java_demangle_sym() previously performed string searches or buffer
allocations before checking whether the symbol name could possibly be a
mangled Rust or Java symbol, adding unnecessary overhead on plain C
symbols.
Add fast-path prefix checks in dso__demangle_sym() before calling
rust_demangle_demangle() (using strstarts() for '_ZN', 'ZN', '__ZN',
'_R', 'R', or '__R', keeping demangle-rust-v0.c unmodified relative to
upstream rustc-demangle) and in java_demangle_sym() (requiring 'L', ';',
'(', and ')') to reject non-Rust and non-Java symbol names immediately.
Using 'perf bench internals symbols':
Before (perf binary):
Average DSO load + sort: 7.348 msec (+- 0.069 msec)
After (perf binary, -4.2%):
Average DSO load + sort: 7.041 msec (+- 0.051 msec)
Before (libLLVM.so.21.1):
Average DSO load + sort: 81.196 msec (+- 0.780 msec)
After (libLLVM.so.21.1, -3.2%):
Average DSO load + sort: 78.599 msec (+- 0.857 msec)
Signed-off-by: Ian Rogers <irogers@xxxxxxxxxx>
---
tools/perf/util/demangle-java.c | 23 +++++++++++++++++++++--
tools/perf/util/symbol.c | 15 ++++++++++++++-
2 files changed, 35 insertions(+), 3 deletions(-)
diff --git a/tools/perf/util/demangle-java.c b/tools/perf/util/demangle-java.c
index c3cb327ed562..5625c4de39eb 100644
--- a/tools/perf/util/demangle-java.c
+++ b/tools/perf/util/demangle-java.c
@@ -158,14 +158,33 @@ char *
java_demangle_sym(const char *str, int flags)
{
char *buf, *ptr;
- const char *p;
+ const char *l, *semi, *open, *p;
size_t len, l1 = 0;
if (!str)
return NULL;
+ /*
+ * OpenJDK JIT symbols have the form:
+ * [prefix]L<class>;<method>(<args>)<return_type>
+ * where the return type (such as 'I' for int or 'V' for void) is at the
+ * end after ')'. In __demangle_java_sym(), the parser starts in
+ * MODE_PREFIX and only reaches MODE_TYPE (required when ')' is reached)
+ * via 'L' (MODE_CLASS) -> ';' (MODE_FUNC) -> '(' (MODE_TYPE). Verify
+ * those delimiters appear in order before allocating the output buffer.
+ */
+ l = strchr(str, 'L');
+ if (!l)
+ return NULL;
+ semi = strchr(l + 1, ';');
+ if (!semi)
+ return NULL;
+ open = strchr(semi + 1, '(');
+ if (!open)
+ return NULL;
+
/* find start of return type */
- p = strrchr(str, ')');
+ p = strrchr(open + 1, ')');
if (!p)
return NULL;
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
index b61a574ce9a3..dc70bbe8514f 100644
--- a/tools/perf/util/symbol.c
+++ b/tools/perf/util/symbol.c
@@ -2738,7 +2738,20 @@ char *dso__demangle_sym(struct dso *dso, int kmodule, const char *elf_name)
if (!want_demangle((dso && dso__kernel(dso)) || kmodule))
return demangled;
- rust_demangle_demangle(elf_name, &rust_demangle);
+ /*
+ * Rust mangled symbols start with one of the prefixes checked by
+ * rust_demangle_legacy_demangle() ("_ZN", Windows dbghelp "ZN", or
+ * macOS "__ZN") or rust_demangle_v0_demangle() ("_R", Windows dbghelp
+ * "R", or macOS "__R") in demangle-rust-v0.c. Check the prefix here so
+ * we avoid calling rust_demangle_demangle() (which scans the entire
+ * symbol name with strstr() and strlen() for ThinLTO ".llvm." suffixes)
+ * on non-Rust symbols, while keeping demangle-rust-v0.c unmodified
+ * relative to upstream rustc-demangle.
+ */
+ if (strstarts(elf_name, "_ZN") || strstarts(elf_name, "ZN") ||
+ strstarts(elf_name, "__ZN") || strstarts(elf_name, "_R") ||
+ strstarts(elf_name, "R") || strstarts(elf_name, "__R"))
+ rust_demangle_demangle(elf_name, &rust_demangle);
if (rust_demangle_is_known(&rust_demangle)) {
/* A rust mangled name. */
if (rust_demangle.mangled_len == 0)
--
2.56.0.313.g2479322e68-goog