From: Ian Rogers <irogers@google.com>
To: irogers@google.com, acme@kernel.org, namhyung@kernel.org
Cc: adrian.hunter@intel.com, haghdoost@uber.com,
james.clark@linaro.org, jolsa@kernel.org,
linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org,
mingo@redhat.com, peterz@infradead.org
Subject: [PATCH v2 11/18] perf symbol-elf: Cache ELF program/section headers and defer symbol name lookup
Date: Tue, 27 Oct 2026 16:15:05 -0700 [thread overview]
Message-ID: <20261027231513.816148-12-irogers@google.com> (raw)
In-Reply-To: <20261027231513.816148-1-irogers@google.com>
Profiling dso__load_sym_internal() on large binaries showed significant
time spent in repeated libelf section and program header lookups
(elf_section_by_idx() and elf_read_program_header()) for each symbol,
resolving elf_sym__name() before filtering out non-symbol/non-allocated
entries, and running symbols__fixup_end() and symbols__fixup_duplicate()
twice when both .symtab and .dynsym are loaded.
Speed up ELF symbol table loading in dso__load_sym_internal() and
dso__load_sym():
- Pre-load PT_LOAD program headers into struct elf_phdr_cache and cache
per-section metadata in a 64-entry direct-mapped table in
dso__load_sym_internal() to avoid repeated libelf section and program
header lookups during symbol iteration.
- Defer elf_sym__name() lookup until after non-allocated and non-symbol
entries have been filtered out, and run symbols__fixup_end() and
symbols__fixup_duplicate() once in dso__load_sym() after loading both
.symtab and .dynsym.
Using 'perf bench internals symbols':
Before (perf binary):
Average DSO load + sort: 5.808 msec (+- 0.047 msec)
After (perf binary, -8.3%):
Average DSO load + sort: 5.324 msec (+- 0.045 msec)
Before (perf binary, --no-demangle):
Average DSO load + sort: 3.948 msec (+- 0.064 msec)
After (perf binary, --no-demangle, -13.7%):
Average DSO load + sort: 3.408 msec (+- 0.034 msec)
Before (libLLVM.so.21.1, --no-demangle):
Average DSO load + sort: 22.433 msec (+- 0.260 msec)
After (libLLVM.so.21.1, --no-demangle, -14.8%):
Average DSO load + sort: 19.122 msec (+- 0.226 msec)
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/symbol-elf.c | 275 +++++++++++++++++++++++++++--------
1 file changed, 217 insertions(+), 58 deletions(-)
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index 4023174ed6db..6082136c9662 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -265,6 +265,118 @@ static int elf_read_program_header(Elf *elf, u64 vaddr, GElf_Phdr *phdr)
return -1;
}
+struct elf_phdr_range {
+ u64 vaddr;
+ u64 end;
+ u64 adjust;
+ GElf_Phdr phdr;
+};
+
+struct elf_phdr_cache {
+ struct elf_phdr_range inline_ranges[16];
+ struct elf_phdr_range *ranges;
+ size_t nr_ranges;
+ size_t last_idx;
+};
+
+static void elf_phdr_cache__init(struct elf_phdr_cache *cache, Elf *elf)
+{
+ size_t i, phdrnum = 0, cnt = 0;
+
+ cache->ranges = cache->inline_ranges;
+ cache->nr_ranges = 0;
+ cache->last_idx = 0;
+
+ if (!elf || elf_getphdrnum(elf, &phdrnum) || phdrnum == 0)
+ return;
+
+ if (phdrnum > ARRAY_SIZE(cache->inline_ranges)) {
+ cache->ranges = calloc(phdrnum, sizeof(*cache->ranges));
+ if (!cache->ranges) {
+ cache->ranges = cache->inline_ranges;
+ phdrnum = ARRAY_SIZE(cache->inline_ranges);
+ }
+ }
+
+ for (i = 0; i < phdrnum; i++) {
+ GElf_Phdr phdr;
+ u64 sz;
+
+ if (gelf_getphdr(elf, i, &phdr) == NULL)
+ break;
+ if (phdr.p_type != PT_LOAD)
+ continue;
+ sz = max(phdr.p_memsz, phdr.p_filesz);
+ if (!sz)
+ continue;
+ cache->ranges[cnt].vaddr = phdr.p_vaddr;
+ cache->ranges[cnt].end = phdr.p_vaddr + sz;
+ cache->ranges[cnt].adjust = phdr.p_vaddr - phdr.p_offset;
+ cache->ranges[cnt].phdr = phdr;
+ cnt++;
+ }
+ cache->nr_ranges = cnt;
+}
+
+static void elf_phdr_cache__exit(struct elf_phdr_cache *cache)
+{
+ if (cache->ranges && cache->ranges != cache->inline_ranges)
+ free(cache->ranges);
+}
+
+static const struct elf_phdr_range *
+elf_phdr_cache__find(struct elf_phdr_cache *cache, u64 vaddr)
+{
+ if (cache->nr_ranges == 0)
+ return NULL;
+
+ if (cache->last_idx < cache->nr_ranges) {
+ const struct elf_phdr_range *r = &cache->ranges[cache->last_idx];
+
+ if (vaddr >= r->vaddr && vaddr < r->end)
+ return r;
+ }
+ for (size_t i = 0; i < cache->nr_ranges; i++) {
+ const struct elf_phdr_range *r = &cache->ranges[i];
+
+ if (vaddr >= r->vaddr && vaddr < r->end) {
+ cache->last_idx = i;
+ return r;
+ }
+ }
+ return NULL;
+}
+
+#define ELF_SEC_CACHE_SIZE 64
+
+struct elf_sec_cache_entry {
+ uint32_t shndx;
+ bool valid;
+ bool is_alloc;
+ bool is_label_sec;
+ bool has_phdr;
+ u64 phdr_vaddr;
+ u64 phdr_end;
+ const char *section_name;
+ GElf_Shdr shdr;
+ GElf_Phdr phdr;
+};
+
+static inline const GElf_Phdr *
+elf_sec_cache__phdr(const struct elf_sec_cache_entry *sce,
+ struct elf_phdr_cache *phdr_cache,
+ u64 vaddr)
+{
+ const struct elf_phdr_range *pr;
+
+ if (sce->has_phdr && vaddr >= sce->phdr_vaddr && vaddr < sce->phdr_end)
+ return &sce->phdr;
+ pr = elf_phdr_cache__find(phdr_cache, vaddr);
+ if (!pr)
+ return NULL;
+ return &pr->phdr;
+}
+
struct rel_info {
u32 nr_entries;
u32 *sorted;
@@ -1543,6 +1655,8 @@ dso__load_sym_internal(struct dso *dso, struct map *map, struct symsrc *syms_ss,
int nr = 0;
bool remap_kernel = false, adjust_kernel_syms = false;
u64 max_text_sh_offset = 0;
+ struct elf_phdr_cache phdr_cache = {};
+ struct elf_sec_cache_entry sec_cache[ELF_SEC_CACHE_SIZE];
if (kmap && !kmaps)
return -1;
@@ -1634,14 +1748,18 @@ dso__load_sym_internal(struct dso *dso, struct map *map, struct symsrc *syms_ss,
if (kmodule && adjust_kernel_syms)
max_text_sh_offset = max_text_section(runtime_ss->elf, &runtime_ss->ehdr);
+ elf_phdr_cache__init(&phdr_cache, runtime_ss->elf);
+ memset(sec_cache, 0, sizeof(sec_cache));
+
curr_dso = dso__get(dso);
elf_symtab__for_each_symbol(syms, nr_syms, idx, sym) {
struct symbol *f;
- const char *elf_name = elf_sym__name(&sym, symstrs);
+ const char *elf_name = NULL;
char *demangled = NULL;
int is_label = elf_sym__is_label(&sym);
const char *section_name;
bool used_opd = false;
+ struct elf_sec_cache_entry *sce;
if (!is_label && !elf_sym__filter(&sym))
continue;
@@ -1652,6 +1770,7 @@ dso__load_sym_internal(struct dso *dso, struct map *map, struct symsrc *syms_ss,
* output:
*/
if (ehdr.e_machine == EM_ARM || ehdr.e_machine == EM_AARCH64) {
+ elf_name = elf_sym__name(&sym, symstrs);
if (elf_name[0] == '$' && strchr("adtx", elf_name[1])
&& (elf_name[2] == '\0' || elf_name[2] == '.'))
continue;
@@ -1659,13 +1778,19 @@ dso__load_sym_internal(struct dso *dso, struct map *map, struct symsrc *syms_ss,
/* Reject RISCV ELF "mapping symbols" */
if (ehdr.e_machine == EM_RISCV) {
+ if (!elf_name)
+ elf_name = elf_sym__name(&sym, symstrs);
if (elf_name[0] == '$' && strchr("dx", elf_name[1]))
continue;
}
/* Reject kernel mapping symbols for kernel DSOs only */
- if (dso__kernel(dso) && is_ignored_kernel_symbol(elf_name))
- continue;
+ if (dso__kernel(dso)) {
+ if (!elf_name)
+ elf_name = elf_sym__name(&sym, symstrs);
+ if (is_ignored_kernel_symbol(elf_name))
+ continue;
+ }
if (runtime_ss->opdsec && sym.st_shndx == runtime_ss->opdidx) {
u32 offset = sym.st_value - syms_ss->opdshdr.sh_addr;
@@ -1688,16 +1813,56 @@ dso__load_sym_internal(struct dso *dso, struct map *map, struct symsrc *syms_ss,
if (sym.st_shndx == SHN_ABS)
continue;
- sec = elf_getscn(syms_ss->elf, sym.st_shndx);
- if (!sec) {
- if (dynsym && ehdr.e_shnum &&
- sym.st_shndx < SHN_LORESERVE &&
- sym.st_shndx >= ehdr.e_shnum)
- continue;
- goto out_elf_end;
- }
+ sce = &sec_cache[sym.st_shndx & (ELF_SEC_CACHE_SIZE - 1)];
+ if (!sce->valid || sce->shndx != sym.st_shndx) {
+ const struct elf_phdr_range *pr;
- gelf_getshdr(sec, &shdr);
+ sec = elf_getscn(syms_ss->elf, sym.st_shndx);
+ if (!sec) {
+ if (dynsym && ehdr.e_shnum &&
+ sym.st_shndx < SHN_LORESERVE &&
+ sym.st_shndx >= ehdr.e_shnum)
+ continue;
+ goto out_elf_end;
+ }
+
+ gelf_getshdr(sec, &shdr);
+ secstrs = secstrs_sym;
+
+ /*
+ * We have to fallback to runtime when syms' section
+ * header has NOBITS set. NOBITS results in file offset
+ * (sh_offset) not being incremented. So sh_offset used
+ * below has different values for syms (invalid) and
+ * runtime (valid).
+ */
+ if (shdr.sh_type == SHT_NOBITS && (shdr.sh_flags & SHF_ALLOC)) {
+ sec = elf_getscn(runtime_ss->elf, sym.st_shndx);
+ if (!sec)
+ goto out_elf_end;
+
+ gelf_getshdr(sec, &shdr);
+ secstrs = secstrs_run;
+ }
+
+ sce->shndx = sym.st_shndx;
+ sce->valid = true;
+ sce->is_alloc = (shdr.sh_flags & SHF_ALLOC) != 0;
+ if (sce->is_alloc) {
+ sce->shdr = shdr;
+ sce->section_name = elf_sec__name(&shdr, secstrs);
+ sce->is_label_sec = elf_sec__filter(&shdr, secstrs) != 0;
+ pr = elf_phdr_cache__find(&phdr_cache, shdr.sh_addr);
+ if (pr) {
+ sce->has_phdr = true;
+ sce->phdr_vaddr = pr->vaddr;
+ sce->phdr_end = pr->end;
+ sce->phdr = pr->phdr;
+ } else {
+ sce->has_phdr = false;
+ }
+ }
+ }
/*
* If the attribute bit SHF_ALLOC is not set, the section
@@ -1707,30 +1872,13 @@ dso__load_sym_internal(struct dso *dso, struct map *map, struct symsrc *syms_ss,
* the section aren't loaded to memory during process execution,
* so skip them.
*/
- if (!(shdr.sh_flags & SHF_ALLOC))
+ if (!sce->is_alloc)
continue;
- secstrs = secstrs_sym;
-
- /*
- * We have to fallback to runtime when syms' section header has
- * NOBITS set. NOBITS results in file offset (sh_offset) not
- * being incremented. So sh_offset used below has different
- * values for syms (invalid) and runtime (valid).
- */
- if (shdr.sh_type == SHT_NOBITS) {
- sec = elf_getscn(runtime_ss->elf, sym.st_shndx);
- if (!sec)
- goto out_elf_end;
-
- gelf_getshdr(sec, &shdr);
- secstrs = secstrs_run;
- }
-
- if (is_label && !elf_sec__filter(&shdr, secstrs))
+ if (is_label && !sce->is_label_sec)
continue;
- section_name = elf_sec__name(&shdr, secstrs);
+ section_name = sce->section_name;
/* On ARM, symbols for thumb functions have 1 added to
* the symbol address as a flag - remove it */
@@ -1740,7 +1888,7 @@ dso__load_sym_internal(struct dso *dso, struct map *map, struct symsrc *syms_ss,
--sym.st_value;
if (dso__kernel(dso)) {
- if (dso__process_kernel_symbol(dso, map, &sym, &shdr,
+ if (dso__process_kernel_symbol(dso, map, &sym, &sce->shdr,
kmaps, kmap, &curr_dso,
section_name,
adjust_kernel_syms,
@@ -1750,17 +1898,20 @@ dso__load_sym_internal(struct dso *dso, struct map *map, struct symsrc *syms_ss,
goto out_elf_end;
} else if ((used_opd && runtime_ss->adjust_symbols) ||
(!used_opd && syms_ss->adjust_symbols)) {
- GElf_Phdr phdr;
+ const GElf_Phdr *phdr;
- if (elf_read_program_header(runtime_ss->elf,
- (u64)sym.st_value, &phdr)) {
+ phdr = elf_sec_cache__phdr(sce, &phdr_cache,
+ (u64)sym.st_value);
+ if (!phdr) {
pr_debug4("%s: failed to find program header for "
"symbol: %s st_value: %#" PRIx64 "\n",
- __func__, elf_name, (u64)sym.st_value);
+ __func__, elf_sym__name(&sym, symstrs),
+ (u64)sym.st_value);
pr_debug4("%s: adjusting symbol: st_value: %#" PRIx64 " "
"sh_addr: %#" PRIx64 " sh_offset: %#" PRIx64 "\n",
- __func__, (u64)sym.st_value, (u64)shdr.sh_addr,
- (u64)shdr.sh_offset);
+ __func__, (u64)sym.st_value,
+ (u64)sce->shdr.sh_addr,
+ (u64)sce->shdr.sh_offset);
/*
* Fail to find program header, let's rollback
* to use shdr.sh_addr and shdr.sh_offset to
@@ -1769,16 +1920,18 @@ dso__load_sym_internal(struct dso *dso, struct map *map, struct symsrc *syms_ss,
* still need to handle java JIT symbols in this
* case.
*/
- sym.st_value -= shdr.sh_addr - shdr.sh_offset;
+ sym.st_value -= sce->shdr.sh_addr - sce->shdr.sh_offset;
} else {
pr_debug4("%s: adjusting symbol: st_value: %#" PRIx64 " "
"p_vaddr: %#" PRIx64 " p_offset: %#" PRIx64 "\n",
- __func__, (u64)sym.st_value, (u64)phdr.p_vaddr,
- (u64)phdr.p_offset);
- sym.st_value -= phdr.p_vaddr - phdr.p_offset;
+ __func__, (u64)sym.st_value, (u64)phdr->p_vaddr,
+ (u64)phdr->p_offset);
+ sym.st_value -= phdr->p_vaddr - phdr->p_offset;
}
}
+ if (!elf_name)
+ elf_name = elf_sym__name(&sym, symstrs);
demangled = dso__demangle_sym(dso, kmodule, elf_name);
if (demangled != NULL)
elf_name = demangled;
@@ -1796,25 +1949,13 @@ dso__load_sym_internal(struct dso *dso, struct map *map, struct symsrc *syms_ss,
goto out_elf_end;
nr++;
}
- dso__put(curr_dso);
- /*
- * For misannotated, zeroed, ASM function sizes.
- */
- if (nr > 0) {
- symbols__fixup_end(dso__symbols(dso), false);
- symbols__fixup_duplicate(dso__symbols(dso));
- if (kmap) {
- /*
- * We need to fixup this here too because we create new
- * maps here, for things like vsyscall sections.
- */
- maps__fixup_end(kmaps);
- }
- }
+ dso__put(curr_dso);
+ elf_phdr_cache__exit(&phdr_cache);
return nr;
out_elf_end:
dso__put(curr_dso);
+ elf_phdr_cache__exit(&phdr_cache);
return -1;
}
@@ -1872,6 +2013,24 @@ int dso__load_sym(struct dso *dso, struct map *map, struct symsrc *syms_ss,
nr += err;
}
+ /*
+ * For misannotated, zeroed, ASM function sizes.
+ */
+ if (nr > 0) {
+ struct kmap *kmap = dso__kernel(dso) ? map__kmap(map) : NULL;
+ struct maps *kmaps = kmap ? map__kmaps(map) : NULL;
+
+ symbols__fixup_end(dso__symbols(dso), false);
+ symbols__fixup_duplicate(dso__symbols(dso));
+ if (kmap && kmaps) {
+ /*
+ * We need to fixup this here too because we create new
+ * maps here, for things like vsyscall sections.
+ */
+ maps__fixup_end(kmaps);
+ }
+ }
+
return nr;
}
--
2.56.0.313.g2479322e68-goog
next prev parent reply other threads:[~2026-10-27 23:15 UTC|newest]
Thread overview: 37+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-28 7:52 [PATCH v1 0/7] perf symbol: Reference counting, flat array storage, and LRU shrinking Ian Rogers
2026-09-28 7:52 ` [PATCH v1 1/7] perf symbol: Add accessor functions for struct symbol fields Ian Rogers
2026-09-28 7:52 ` [PATCH v1 2/7] perf symbol: Remove symbol_conf.priv_size and negative-offset allocations Ian Rogers
2026-09-28 7:52 ` [PATCH v1 3/7] perf symbol: Switch backing storage from rbtree to struct symbols array Ian Rogers
2026-09-28 7:52 ` [PATCH v1 4/7] perf symbol: Add reference counting and DECLARE_RC_STRUCT(symbol) Ian Rogers
2026-09-28 7:52 ` [PATCH v1 5/7] perf symbol: Add LRU memory shrinking for symbols, DSOs, and machines Ian Rogers
2026-09-28 7:52 ` [PATCH v1 6/7] perf session: Periodically shrink symbols and DSOs during event processing Ian Rogers
2026-09-28 7:52 ` [PATCH v1 7/7] perf test symbols: Add tests for symbol and DSO LRU shrinking Ian Rogers
2026-09-28 15:37 ` [PATCH v1 0/7] perf symbol: Reference counting, flat array storage, and " Ian Rogers
2026-09-28 19:45 ` Alireza Haghdoost
2026-09-28 20:55 ` Ian Rogers
2026-09-28 21:51 ` Namhyung Kim
2026-09-28 22:04 ` Ian Rogers
2026-09-29 0:12 ` Alireza Haghdoost
2026-09-29 18:09 ` Namhyung Kim
2026-09-29 21:24 ` Alireza Haghdoost
2026-09-29 18:06 ` Namhyung Kim
2026-10-27 23:14 ` [PATCH v2 00/18] perf symbol: Reference counting, sorted arrays, and memory shrinking Ian Rogers
2026-10-27 23:14 ` [PATCH v2 01/18] perf bench internals: Add symbols and maps benchmarks Ian Rogers
2026-10-27 23:14 ` [PATCH v2 02/18] perf demangle: Add fast-path prefix checks for Rust and Java demanglers Ian Rogers
2026-10-27 23:14 ` [PATCH v2 03/18] perf symbol: Add accessor functions for struct symbol fields Ian Rogers
2026-10-27 23:14 ` [PATCH v2 04/18] perf map_browser: Compute symbol browser index on demand Ian Rogers
2026-10-27 23:14 ` [PATCH v2 05/18] perf db-export: Store symbol database IDs in a hashmap Ian Rogers
2026-10-27 23:15 ` [PATCH v2 06/18] perf annotate: Move struct annotation to a hashmap and remove symbol_conf.priv_size Ian Rogers
2026-10-27 23:15 ` [PATCH v2 07/18] perf symbol: Switch backing storage from rbtree to struct symbols array Ian Rogers
2026-10-27 23:15 ` [PATCH v2 08/18] perf symbol: Add reference counting and DECLARE_RC_STRUCT(symbol) Ian Rogers
2026-10-27 23:15 ` [PATCH v2 09/18] perf symbol: Optimize symbol sorting with natural-run merge sort and binary insertion Ian Rogers
2026-10-27 23:15 ` [PATCH v2 10/18] perf maps: Optimize tail insertion and small-append sorting Ian Rogers
2026-10-27 23:15 ` Ian Rogers [this message]
2026-10-27 23:15 ` [PATCH v2 12/18] perf rwsem: Add down_read_trylock() and down_write_trylock() Ian Rogers
2026-10-27 23:15 ` [PATCH v2 13/18] perf vdso: Constify dso parameter in dso__is_vdso() Ian Rogers
2026-10-27 23:15 ` [PATCH v2 14/18] perf machine: Protect machines->guests rbtree with a rw_semaphore Ian Rogers
2026-10-27 23:15 ` [PATCH v2 15/18] perf symbol: Add memory shrinking for symbols, DSOs, and machines Ian Rogers
2026-10-27 23:15 ` [PATCH v2 16/18] perf session: Add dso__session() and session-scoped allocation wrappers Ian Rogers
2026-10-27 23:15 ` [PATCH v2 17/18] perf symbol: Use session allocation wrappers in symbol, DSO, thread, and hist paths Ian Rogers
2026-10-27 23:15 ` [PATCH v2 18/18] perf test symbols: Add tests for symbol shrinking and session alloc wrappers Ian Rogers
2026-10-28 20:29 ` [PATCH v2 00/18] perf symbol: Reference counting, sorted arrays, and memory shrinking Ian Rogers
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261027231513.816148-12-irogers@google.com \
--to=irogers@google.com \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=haghdoost@uber.com \
--cc=james.clark@linaro.org \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=mingo@redhat.com \
--cc=namhyung@kernel.org \
--cc=peterz@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®