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: 52+ 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-29 19:46 ` Alireza Haghdoost
2026-11-01 2:48 ` Namhyung Kim
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-30 4:08 ` Alireza Haghdoost
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-30 12:51 ` Alireza Haghdoost
2026-10-27 23:15 ` [PATCH v2 07/18] perf symbol: Switch backing storage from rbtree to struct symbols array Ian Rogers
2026-10-30 13:12 ` Alireza Haghdoost
2026-10-27 23:15 ` [PATCH v2 08/18] perf symbol: Add reference counting and DECLARE_RC_STRUCT(symbol) Ian Rogers
2026-10-30 13:22 ` Alireza Haghdoost
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-30 13:27 ` Alireza Haghdoost
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-30 13:32 ` [PATCH v2 11/18] perf symbol-elf: Cache ELF program/section headers and defer symbol name lookup Alireza Haghdoost
2026-10-27 23:15 ` [PATCH v2 12/18] perf rwsem: Add down_read_trylock() and down_write_trylock() Ian Rogers
2026-10-30 13:39 ` Alireza Haghdoost
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-30 13:53 ` Alireza Haghdoost
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-30 13:55 ` Alireza Haghdoost
2026-10-28 20:29 ` [PATCH v2 00/18] perf symbol: Reference counting, sorted arrays, and memory shrinking Ian Rogers
2026-10-30 14:02 ` Alireza Haghdoost
2026-10-30 16:10 ` Ian Rogers
2026-10-30 17:57 ` Ian Rogers
2026-10-31 17:22 ` David Laight
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.