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 15/18] perf symbol: Add memory shrinking for symbols, DSOs, and machines
Date: Tue, 27 Oct 2026 16:15:09 -0700 [thread overview]
Message-ID: <20261027231513.816148-16-irogers@google.com> (raw)
In-Reply-To: <20261027231513.816148-1-irogers@google.com>
Long-running 'perf top' and large 'perf report' sessions can load full
symbol tables, cached 4KB data pages, addr2line subprocesses, and libdw
contexts across thousands of DSOs even when only a small fraction of
symbols are actively referenced by live hist_entry or callchain nodes.
Add on-demand memory shrinking functions to reclaim cached symbol, DSO,
and debuginfo state under memory pressure:
- symbols__shrink(): Evict symbols whose only reference is the struct
symbols container (refcnt == 1 and not annotated) while retaining
symbols actively referenced by callers (e.g. hist_entry, map_symbol,
callchain nodes, or annotations). Mark the symbol table as shrunk and
clear dso->loaded when symbols are evicted so subsequent map__load()
calls reload the DSO under dso__lock(dso) and merge retained symbol
instances back in place.
- dso__shrink(): Close open file descriptors, free cached 4KB data
pages (on file-backed user DSOs), addr2line handles, and libdw Dwfl
contexts, and shrink reloadable user DSO symbol tables.
- dsos__shrink(), machine__shrink(), machines__shrink(), and
perf_session__shrink(): Shrink all DSOs across host and guest
machines, evict unreferenced user DSOs with no remaining symbols, and
return free heap pages via malloc_trim(0) on glibc.
All shrinking paths use non-blocking trylocks and perform zero heap
allocations so they can be safely invoked from OOM recovery paths.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/dso.c | 192 ++++++++++++++++++--
tools/perf/util/dso.h | 24 ++-
tools/perf/util/dsos.c | 68 +++++++
tools/perf/util/dsos.h | 2 +
tools/perf/util/machine.c | 32 ++++
tools/perf/util/machine.h | 3 +
tools/perf/util/session.c | 8 +
tools/perf/util/session.h | 2 +
tools/perf/util/symbol-elf.c | 2 +-
tools/perf/util/symbol.c | 332 ++++++++++++++++++++++++++++++-----
tools/perf/util/symbol.h | 6 +
11 files changed, 609 insertions(+), 62 deletions(-)
diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c
index 35a5c65030af..be257c77ab0a 100644
--- a/tools/perf/util/dso.c
+++ b/tools/perf/util/dso.c
@@ -1075,12 +1075,12 @@ static int bpf_size(struct dso *dso)
#endif // HAVE_LIBBPF_SUPPORT
static void
-dso_cache__free(struct dso *dso)
+__dso_cache__free(struct dso *dso)
+ EXCLUSIVE_LOCKS_REQUIRED(dso__lock(dso))
{
struct rb_root *root = &dso__data(dso)->cache;
struct rb_node *next = rb_first(root);
- mutex_lock(dso__lock(dso));
while (next) {
struct dso_cache *cache;
@@ -1089,6 +1089,13 @@ dso_cache__free(struct dso *dso)
rb_erase(&cache->rb_node, root);
free(cache);
}
+}
+
+static void
+dso_cache__free(struct dso *dso)
+{
+ mutex_lock(dso__lock(dso));
+ __dso_cache__free(dso);
mutex_unlock(dso__lock(dso));
}
@@ -1243,27 +1250,34 @@ static struct dso_cache *dso_cache__populate(struct dso *dso,
return cache;
}
-static struct dso_cache *dso_cache__find(struct dso *dso,
- struct machine *machine,
- u64 offset,
- ssize_t *ret)
-{
- struct dso_cache *cache = __dso_cache__find(dso, offset);
-
- return cache ? cache : dso_cache__populate(dso, machine, offset, ret);
-}
-
static ssize_t dso_cache_io(struct dso *dso, struct machine *machine,
u64 offset, u8 *data, ssize_t size, bool out)
{
struct dso_cache *cache;
ssize_t ret = 0;
- cache = dso_cache__find(dso, machine, offset, &ret);
- if (!cache)
- return ret;
+ mutex_lock(dso__lock(dso));
+ cache = __dso_cache__find(dso, offset);
+ if (!cache) {
+ mutex_unlock(dso__lock(dso));
+ cache = dso_cache__populate(dso, machine, offset, &ret);
+ if (!cache)
+ return ret;
+ mutex_lock(dso__lock(dso));
+ /*
+ * Re-lookup under dso__lock(dso) in case dso__shrink() freed
+ * the cache while the lock was dropped.
+ */
+ cache = __dso_cache__find(dso, offset);
+ if (!cache) {
+ mutex_unlock(dso__lock(dso));
+ return -EAGAIN;
+ }
+ }
- return dso_cache__memcpy(cache, offset, data, size, out);
+ ret = dso_cache__memcpy(cache, offset, data, size, out);
+ mutex_unlock(dso__lock(dso));
+ return ret;
}
/*
@@ -1803,7 +1817,8 @@ int dso__name_len(const struct dso *dso)
bool dso__loaded(const struct dso *dso)
{
- return RC_CHK_ACCESS(dso)->loaded;
+ return atomic_load_explicit(&RC_CHK_ACCESS(dso)->loaded, memory_order_acquire) &&
+ !dso__symbols_shrunk(dso);
}
bool dso__sorted_by_name(const struct dso *dso)
@@ -1835,7 +1850,7 @@ struct dso *dso__new_id(const char *name, const struct dso_id *id)
dso->binary_type = DSO_BINARY_TYPE__NOT_FOUND;
dso->dbginfo_type = DSO_BINARY_TYPE__NOT_FOUND;
dso->is_64_bit = (sizeof(void *) == 8);
- dso->loaded = 0;
+ atomic_init(&dso->loaded, false);
dso->rel = 0;
dso->has_srcline = 1;
dso->a2l_fails = 1;
@@ -1896,6 +1911,144 @@ void dso__delete(struct dso *dso)
RC_CHK_FREE(dso);
}
+bool dso__can_shrink_symbols(const struct dso *dso)
+{
+ const char *name;
+
+ if (!dso || (!dso__loaded(dso) && !dso__symbols_shrunk(dso)))
+ return false;
+
+ if (dso__kernel(dso) || dso__is_kmod(dso))
+ return false;
+
+ if (dso__is_vdso(dso))
+ return false;
+
+ switch (dso__binary_type(dso)) {
+ case DSO_BINARY_TYPE__KALLSYMS:
+ case DSO_BINARY_TYPE__GUEST_KALLSYMS:
+ case DSO_BINARY_TYPE__VMLINUX:
+ case DSO_BINARY_TYPE__GUEST_VMLINUX:
+ case DSO_BINARY_TYPE__KCORE:
+ case DSO_BINARY_TYPE__GUEST_KCORE:
+ case DSO_BINARY_TYPE__BPF_PROG_INFO:
+ case DSO_BINARY_TYPE__BPF_IMAGE:
+ case DSO_BINARY_TYPE__OOL:
+ case DSO_BINARY_TYPE__JAVA_JIT:
+ case DSO_BINARY_TYPE__GUEST_KMODULE:
+ case DSO_BINARY_TYPE__GUEST_KMODULE_COMP:
+ case DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE:
+ case DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE_COMP:
+ return false;
+ case DSO_BINARY_TYPE__DEBUGLINK:
+ case DSO_BINARY_TYPE__BUILD_ID_CACHE:
+ case DSO_BINARY_TYPE__BUILD_ID_CACHE_DEBUGINFO:
+ case DSO_BINARY_TYPE__FEDORA_DEBUGINFO:
+ case DSO_BINARY_TYPE__UBUNTU_DEBUGINFO:
+ case DSO_BINARY_TYPE__MIXEDUP_UBUNTU_DEBUGINFO:
+ case DSO_BINARY_TYPE__BUILDID_DEBUGINFO:
+ case DSO_BINARY_TYPE__GNU_DEBUGDATA:
+ case DSO_BINARY_TYPE__SYSTEM_PATH_DSO:
+ case DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO:
+ case DSO_BINARY_TYPE__NOT_FOUND:
+ default:
+ break;
+ }
+
+ switch (dso__symtab_type(dso)) {
+ case DSO_BINARY_TYPE__KALLSYMS:
+ case DSO_BINARY_TYPE__GUEST_KALLSYMS:
+ case DSO_BINARY_TYPE__VMLINUX:
+ case DSO_BINARY_TYPE__GUEST_VMLINUX:
+ case DSO_BINARY_TYPE__KCORE:
+ case DSO_BINARY_TYPE__GUEST_KCORE:
+ case DSO_BINARY_TYPE__BPF_PROG_INFO:
+ case DSO_BINARY_TYPE__BPF_IMAGE:
+ case DSO_BINARY_TYPE__OOL:
+ case DSO_BINARY_TYPE__JAVA_JIT:
+ case DSO_BINARY_TYPE__GUEST_KMODULE:
+ case DSO_BINARY_TYPE__GUEST_KMODULE_COMP:
+ case DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE:
+ case DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE_COMP:
+ case DSO_BINARY_TYPE__NOT_FOUND:
+ return false;
+ case DSO_BINARY_TYPE__DEBUGLINK:
+ case DSO_BINARY_TYPE__BUILD_ID_CACHE:
+ case DSO_BINARY_TYPE__BUILD_ID_CACHE_DEBUGINFO:
+ case DSO_BINARY_TYPE__FEDORA_DEBUGINFO:
+ case DSO_BINARY_TYPE__UBUNTU_DEBUGINFO:
+ case DSO_BINARY_TYPE__MIXEDUP_UBUNTU_DEBUGINFO:
+ case DSO_BINARY_TYPE__BUILDID_DEBUGINFO:
+ case DSO_BINARY_TYPE__GNU_DEBUGDATA:
+ case DSO_BINARY_TYPE__SYSTEM_PATH_DSO:
+ case DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO:
+ default:
+ break;
+ }
+
+ name = dso__long_name(dso);
+ if (!name || name[0] == '[' || is_perf_pid_map_name(name) ||
+ strstr(name, " (deleted)") != NULL)
+ return false;
+
+ return true;
+}
+
+size_t dso__shrink(struct dso *dso)
+ NO_THREAD_SAFETY_ANALYSIS
+{
+ size_t freed = 0;
+
+ if (!dso)
+ return 0;
+
+ if (mutex_trylock(dso__data_open_lock())) {
+ if (dso__data(dso)->fd >= 0) {
+ close_dso(dso);
+ freed++;
+ }
+ mutex_unlock(dso__data_open_lock());
+ }
+
+ if (!mutex_trylock(dso__lock(dso)))
+ return freed;
+
+ if (!dso__kernel(dso) && dso__binary_type(dso) != DSO_BINARY_TYPE__OOL &&
+ !RB_EMPTY_ROOT(&dso__data(dso)->cache)) {
+ __dso_cache__free(dso);
+ freed++;
+ }
+ if (dso__a2l(dso)) {
+ dso__free_a2l(dso);
+ freed++;
+ }
+#ifdef HAVE_LIBBFD_SUPPORT
+ if (dso__a2l_libbfd(dso)) {
+ dso__free_a2l_libbfd(dso);
+ freed++;
+ }
+#endif
+#ifdef HAVE_LIBDW_SUPPORT
+ if (dso__libdw(dso)) {
+ dso__free_libdw(dso);
+ freed++;
+ }
+#endif
+
+ if (dso__can_shrink_symbols(dso)) {
+ size_t sym_freed = symbols__shrink(dso__symbols(dso));
+
+ if (sym_freed > 0) {
+ atomic_store_explicit(&RC_CHK_ACCESS(dso)->loaded, false,
+ memory_order_release);
+ freed += sym_freed;
+ }
+ }
+
+ mutex_unlock(dso__lock(dso));
+ return freed;
+}
+
struct dso *dso__get(struct dso *dso)
{
struct dso *result;
@@ -1909,7 +2062,8 @@ struct dso *dso__get(struct dso *dso)
void dso__put(struct dso *dso)
{
#ifdef REFCNT_CHECKING
- if (dso && dso__data(dso) && refcount_read(&RC_CHK_ACCESS(dso)->refcnt) == 2)
+ if (dso && dso__data(dso) && dso__data(dso)->fd >= 0 &&
+ refcount_read(&RC_CHK_ACCESS(dso)->refcnt) == 2)
dso__data_close(dso);
#endif
if (dso && refcount_dec_and_test(&RC_CHK_ACCESS(dso)->refcnt))
diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h
index f23cf9957aec..64a118ea2524 100644
--- a/tools/perf/util/dso.h
+++ b/tools/perf/util/dso.h
@@ -346,7 +346,7 @@ DECLARE_RC_STRUCT(dso) {
u8 is_64_bit:1;
u8 bintype_searched:1;
u8 debuginfo_searched:1;
- bool loaded;
+ _Atomic bool loaded;
u8 rel;
char name[];
};
@@ -602,7 +602,7 @@ static inline enum dso_load_errno *dso__load_errno(struct dso *dso)
static inline void dso__set_loaded(struct dso *dso)
{
- RC_CHK_ACCESS(dso)->loaded = true;
+ atomic_store_explicit(&RC_CHK_ACCESS(dso)->loaded, true, memory_order_release);
}
static inline struct mutex *dso__lock(struct dso *dso)
@@ -783,6 +783,26 @@ static inline bool dso__has_symbols(const struct dso *dso)
return RC_CHK_ACCESS(dso)->symbols.cnt > 0;
}
+static inline bool dso__symbols_shrunk(const struct dso *dso)
+{
+ return atomic_load_explicit(&RC_CHK_ACCESS(dso)->symbols.shrunk,
+ memory_order_acquire);
+}
+
+static inline void dso__set_symbols_shrunk(struct dso *dso, bool val)
+{
+ atomic_store_explicit(&RC_CHK_ACCESS(dso)->symbols.shrunk, val,
+ memory_order_release);
+}
+
+static inline refcount_t *dso__refcnt(struct dso *dso)
+{
+ return &RC_CHK_ACCESS(dso)->refcnt;
+}
+
+bool dso__can_shrink_symbols(const struct dso *dso);
+size_t dso__shrink(struct dso *dso);
+
char *dso__filename_with_chroot(const struct dso *dso, const char *filename);
bool dso__sorted_by_name(const struct dso *dso);
diff --git a/tools/perf/util/dsos.c b/tools/perf/util/dsos.c
index e927e707abac..30ecc9a19244 100644
--- a/tools/perf/util/dsos.c
+++ b/tools/perf/util/dsos.c
@@ -6,6 +6,9 @@
#include "vdso.h"
#include "namespaces.h"
#include <errno.h>
+#ifdef __GLIBC__
+#include <malloc.h>
+#endif
#include <stdlib.h>
#include <string.h>
#include <symbol.h> // filename__read_build_id
@@ -493,3 +496,68 @@ int dsos__for_each_dso(struct dsos *dsos, int (*cb)(struct dso *dso, void *data)
up_read(&dsos->lock);
return err;
}
+
+size_t dsos__shrink(struct dsos *dsos)
+ NO_THREAD_SAFETY_ANALYSIS
+{
+ unsigned int write_idx = 0;
+ size_t freed = 0;
+ bool any_removed = false;
+
+ if (!down_write_trylock(&dsos->lock))
+ return 0;
+
+ for (unsigned int read_idx = 0; read_idx < dsos->cnt; read_idx++) {
+ struct dso *dso = dsos->dsos[read_idx];
+
+ freed += dso__shrink(dso);
+
+ if (!dso__has_symbols(dso) &&
+ !dso__header_build_id(dso) &&
+ !dso__hit(dso) &&
+ !dso__kernel(dso) &&
+ refcount_read(dso__refcnt(dso)) == 1 &&
+ mutex_trylock(&_dso__data_open_lock)) {
+ bool can_evict = dso__data(dso)->fd < 0 &&
+ list_empty(&dso__data(dso)->open_entry) &&
+ mutex_trylock(dso__lock(dso));
+
+ if (can_evict)
+ mutex_unlock(dso__lock(dso));
+ mutex_unlock(&_dso__data_open_lock);
+ if (can_evict) {
+ dso__set_dsos(dso, NULL);
+ dso__put(dso);
+ any_removed = true;
+ freed++;
+ continue;
+ }
+ }
+ dsos->dsos[write_idx++] = dso;
+ }
+
+ dsos->cnt = write_idx;
+ if (any_removed) {
+ if (write_idx == 0) {
+ zfree(&dsos->dsos);
+ dsos->allocated = 0;
+ dsos->sorted = true;
+ } else if (write_idx < dsos->allocated / 2) {
+ struct dso **shrunk = realloc(dsos->dsos,
+ write_idx * sizeof(struct dso *));
+ if (shrunk) {
+ dsos->dsos = shrunk;
+ dsos->allocated = write_idx;
+ }
+ }
+ }
+
+ up_write(&dsos->lock);
+
+#ifdef __GLIBC__
+ if (freed > 0)
+ malloc_trim(0);
+#endif
+
+ return freed;
+}
diff --git a/tools/perf/util/dsos.h b/tools/perf/util/dsos.h
index a26774950866..151efdba2aa4 100644
--- a/tools/perf/util/dsos.h
+++ b/tools/perf/util/dsos.h
@@ -49,4 +49,6 @@ struct dso *dsos__find_kernel_dso(struct dsos *dsos);
int dsos__for_each_dso(struct dsos *dsos, int (*cb)(struct dso *dso, void *data), void *data);
+size_t dsos__shrink(struct dsos *dsos);
+
#endif /* __PERF_DSOS */
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index fb83da8ef09b..8d0c545ee544 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -3446,3 +3446,35 @@ int machine__hit_all_dsos(struct machine *machine)
{
return dsos__hit_all(&machine->dsos);
}
+
+size_t machine__shrink(struct machine *machine)
+{
+ if (!machine)
+ return 0;
+
+ return dsos__shrink(&machine->dsos);
+}
+
+size_t machines__shrink(struct machines *machines)
+ NO_THREAD_SAFETY_ANALYSIS
+{
+ struct rb_node *nd;
+ size_t freed;
+
+ if (!machines)
+ return 0;
+
+ freed = machine__shrink(&machines->host);
+
+ if (!down_read_trylock(&machines->lock))
+ return freed;
+
+ for (nd = rb_first_cached(&machines->guests); nd; nd = rb_next(nd)) {
+ struct machine *pos = rb_entry(nd, struct machine, rb_node);
+
+ freed += machine__shrink(pos);
+ }
+ up_read(&machines->lock);
+
+ return freed;
+}
diff --git a/tools/perf/util/machine.h b/tools/perf/util/machine.h
index 147ce343e370..1d1f46a01a38 100644
--- a/tools/perf/util/machine.h
+++ b/tools/perf/util/machine.h
@@ -337,4 +337,7 @@ int machine__resolve(struct machine *machine, struct addr_location *al,
int machine__hit_all_dsos(struct machine *machine);
+size_t machine__shrink(struct machine *machine);
+size_t machines__shrink(struct machines *machines);
+
#endif /* __PERF_MACHINE_H */
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index 56a5e57ec512..3f35c7affc29 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -4266,3 +4266,11 @@ uint16_t perf_session__e_machine(struct perf_session *session, uint32_t *e_flags
*/
return perf_env__e_machine_nocache(env, e_flags);
}
+
+size_t perf_session__shrink(struct perf_session *session)
+{
+ if (!session)
+ return 0;
+
+ return machines__shrink(&session->machines);
+}
diff --git a/tools/perf/util/session.h b/tools/perf/util/session.h
index ac5803d5fb4e..fdec65fcfee6 100644
--- a/tools/perf/util/session.h
+++ b/tools/perf/util/session.h
@@ -217,4 +217,6 @@ int perf_event__process_finished_round(const struct perf_tool *tool,
struct perf_env *perf_session__env(struct perf_session *session);
uint16_t perf_session__e_machine(struct perf_session *session, uint32_t *e_flags);
+size_t perf_session__shrink(struct perf_session *session);
+
#endif /* __PERF_SESSION_H */
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index 6082136c9662..946a2426933c 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -1973,7 +1973,7 @@ int dso__load_sym(struct dso *dso, struct map *map, struct symsrc *syms_ss,
* Modules may already have symbols from kallsyms, but those symbols
* have the wrong values for the dso maps, so remove them.
*/
- if (kmodule && syms_ss->symtab)
+ if (kmodule && syms_ss->symtab && !dso__symbols_shrunk(dso))
symbols__delete(dso__symbols(dso));
if (!syms_ss->symtab) {
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
index 7f1fcd5c64b2..e5338c8e03dc 100644
--- a/tools/perf/util/symbol.c
+++ b/tools/perf/util/symbol.c
@@ -164,6 +164,18 @@ static int choose_best_symbol(struct symbol *syma, struct symbol *symb)
s64 b;
size_t na, nb;
+ /*
+ * Prefer a symbol that already has external references (e.g. retained
+ * across a symbols__shrink() and subsequent reload) so existing
+ * callers continue pointing to the canonical symbol in the DSO.
+ */
+ a = refcount_read(symbol__refcnt(syma)) > 1 || symbol__annotated(syma);
+ b = refcount_read(symbol__refcnt(symb)) > 1 || symbol__annotated(symb);
+ if (a && !b)
+ return SYMBOL_A;
+ if (b && !a)
+ return SYMBOL_B;
+
/* Prefer a symbol with non zero length */
a = symbol__end(syma) - symbol__start(syma);
b = symbol__end(symb) - symbol__start(symb);
@@ -225,6 +237,7 @@ void symbols__init(struct symbols *symbols)
symbols->num_by_name = 0;
symbols->sorted = true;
symbols->sorted_by_name = false;
+ atomic_init(&symbols->shrunk, false);
}
static void symbols__delete_locked(struct symbols *symbols)
@@ -240,6 +253,7 @@ static void symbols__delete_locked(struct symbols *symbols)
symbols->num_by_name = 0;
symbols->sorted = true;
symbols->sorted_by_name = false;
+ atomic_store_explicit(&symbols->shrunk, false, memory_order_release);
}
void symbols__delete(struct symbols *symbols)
@@ -261,6 +275,70 @@ void symbols__exit(struct symbols *symbols)
exit_rwsem(&symbols->lock);
}
+/*
+ * Evict symbols whose only reference is the @symbols array itself
+ * (refcnt == 1), retaining symbols referenced by active callers (e.g.
+ * hist_entry, map_symbol, or callchain nodes with refcnt > 1).
+ *
+ * Uses down_write_trylock() so this is safe to call from OOM recovery
+ * paths where the current thread might already hold a lock on @symbols.
+ *
+ * Returns the number of symbols evicted.
+ */
+size_t symbols__shrink(struct symbols *symbols)
+{
+ unsigned int write_idx = 0;
+ unsigned int old_sorted_cnt;
+ unsigned int new_sorted_cnt = 0;
+ size_t freed = 0;
+
+ if (!down_write_trylock(&symbols->lock))
+ return 0;
+
+ old_sorted_cnt = symbols->sorted ? symbols->cnt : symbols->sorted_cnt;
+
+ for (unsigned int read_idx = 0; read_idx < symbols->cnt; read_idx++) {
+ struct symbol *sym = symbols->symbols[read_idx];
+
+ if (refcount_read(symbol__refcnt(sym)) > 1 || symbol__annotated(sym)) {
+ if (read_idx < old_sorted_cnt)
+ new_sorted_cnt++;
+ symbols->symbols[write_idx++] = sym;
+ } else {
+ symbol__put(sym);
+ freed++;
+ }
+ }
+
+ symbols->cnt = write_idx;
+ symbols->sorted_cnt = new_sorted_cnt;
+ if (new_sorted_cnt == write_idx)
+ symbols->sorted = true;
+
+ if (freed > 0) {
+ zfree(&symbols->symbols_by_name);
+ symbols->num_by_name = 0;
+ symbols->sorted_by_name = false;
+ atomic_store_explicit(&symbols->shrunk, true, memory_order_release);
+
+ if (write_idx == 0) {
+ zfree(&symbols->symbols);
+ symbols->allocated = 0;
+ symbols->sorted = true;
+ } else if (write_idx < symbols->allocated / 2) {
+ struct symbol **shrunk = realloc(symbols->symbols,
+ write_idx * sizeof(struct symbol *));
+ if (shrunk) {
+ symbols->symbols = shrunk;
+ symbols->allocated = write_idx;
+ }
+ }
+ }
+
+ up_write(&symbols->lock);
+ return freed;
+}
+
static inline bool symbol__less_or_equal(const struct symbol *a, const struct symbol *b)
{
if (symbol__start(a) != symbol__start(b))
@@ -602,9 +680,7 @@ void symbols__sort_read_lock(struct symbols *symbols)
void symbols__fixup_duplicate(struct symbols *symbols)
{
unsigned int write_idx = 0;
-
- if (symbol_conf.allow_aliases)
- return;
+ unsigned int group_start = 0;
down_write(&symbols->lock);
symbols__sort_locked(symbols);
@@ -614,14 +690,37 @@ void symbols__fixup_duplicate(struct symbols *symbols)
}
for (unsigned int read_idx = 1; read_idx < symbols->cnt; read_idx++) {
- struct symbol *curr = symbols->symbols[write_idx];
struct symbol *next = symbols->symbols[read_idx];
+ struct symbol *curr;
+ unsigned int match_idx = write_idx;
+ bool matched = false;
- if (symbol__start(curr) != symbol__start(next)) {
+ if (symbol__start(symbols->symbols[group_start]) != symbol__start(next)) {
symbols->symbols[++write_idx] = next;
+ group_start = write_idx;
continue;
}
+ if (!symbol_conf.allow_aliases) {
+ matched = true;
+ } else {
+ for (unsigned int k = group_start; k <= write_idx; k++) {
+ if (strcmp(symbol__name(symbols->symbols[k]),
+ symbol__name(next)) == 0) {
+ match_idx = k;
+ matched = true;
+ break;
+ }
+ }
+ }
+
+ if (!matched) {
+ symbols->symbols[++write_idx] = next;
+ continue;
+ }
+
+ curr = symbols->symbols[match_idx];
+
if (choose_best_symbol(curr, next) == SYMBOL_A) {
if (symbol__type(next) == STT_GNU_IFUNC)
symbol__set_ifunc_alias(curr, true);
@@ -630,7 +729,13 @@ void symbols__fixup_duplicate(struct symbols *symbols)
if (symbol__type(curr) == STT_GNU_IFUNC)
symbol__set_ifunc_alias(next, true);
symbol__put(curr);
- symbols->symbols[write_idx] = next;
+ symbols->symbols[match_idx] = next;
+ while (match_idx < write_idx &&
+ !symbol__less_or_equal(next, symbols->symbols[match_idx + 1])) {
+ symbols->symbols[match_idx] = symbols->symbols[match_idx + 1];
+ symbols->symbols[match_idx + 1] = next;
+ match_idx++;
+ }
}
}
symbols->cnt = write_idx + 1;
@@ -676,44 +781,47 @@ void symbols__fixup_end(struct symbols *symbols, bool is_kallsyms)
* like in:
* ffffffffc1937000 T hdmi_driver_init [snd_hda_codec_hdmi]
*/
- if (symbol__end(prev) == symbol__start(prev) &&
- symbol__start(prev) != symbol__start(curr)) {
- const char *prev_mod;
- const char *curr_mod;
+ if (symbol__start(prev) != symbol__start(curr)) {
+ if (symbol__end(prev) == symbol__start(prev)) {
+ const char *prev_mod;
+ const char *curr_mod;
- if (!is_kallsyms) {
- symbol__set_end(prev, symbol__start(curr));
- } else {
- prev_mod = strchr(symbol__name(prev), '[');
- curr_mod = strchr(symbol__name(curr), '[');
-
- /* Last kernel/module symbol mapped to end of page */
- if (!prev_mod != !curr_mod) {
- symbol__set_end(prev,
- roundup(symbol__end(prev) + 4096, 4096));
- /* Last symbol in the previous module */
- } else if (prev_mod && strcmp(prev_mod, curr_mod)) {
- symbol__set_end(prev,
- roundup(symbol__end(prev) + 4096, 4096));
- } else {
+ if (!is_kallsyms) {
symbol__set_end(prev, symbol__start(curr));
- }
+ } else {
+ prev_mod = strchr(symbol__name(prev), '[');
+ curr_mod = strchr(symbol__name(curr), '[');
+
+ /* Last kernel/module symbol mapped to end of page */
+ if (!prev_mod != !curr_mod) {
+ symbol__set_end(prev,
+ roundup(symbol__end(prev) + 4096,
+ 4096));
+ /* Last symbol in the previous module */
+ } else if (prev_mod && strcmp(prev_mod, curr_mod)) {
+ symbol__set_end(prev,
+ roundup(symbol__end(prev) + 4096,
+ 4096));
+ } else {
+ symbol__set_end(prev, symbol__start(curr));
+ }
- /* The next module can start within that page */
- if (symbol__end(prev) > symbol__start(curr))
- symbol__set_end(prev, symbol__start(curr));
+ /* The next module can start within that page */
+ if (symbol__end(prev) > symbol__start(curr))
+ symbol__set_end(prev, symbol__start(curr));
- pr_debug4("%s sym:%s end:%#" PRIx64 "\n",
- __func__, symbol__name(prev), symbol__end(prev));
- }
+ pr_debug4("%s sym:%s end:%#" PRIx64 "\n",
+ __func__, symbol__name(prev), symbol__end(prev));
+ }
- for (unsigned int k = i - 1; k > 0; k--) {
- struct symbol *same = symbols->symbols[k - 1];
+ for (unsigned int k = i - 1; k > 0; k--) {
+ struct symbol *same = symbols->symbols[k - 1];
- if (symbol__start(same) != symbol__start(prev) ||
- symbol__end(same) != symbol__start(same))
- break;
- symbol__set_end(same, symbol__end(prev));
+ if (symbol__start(same) != symbol__start(prev) ||
+ symbol__end(same) != symbol__start(same))
+ break;
+ symbol__set_end(same, symbol__end(prev));
+ }
}
}
}
@@ -860,6 +968,118 @@ static int __symbols__insert(struct symbols *symbols, struct symbol *sym)
return 0;
}
+static void symbols__detach_retained(struct symbols *symbols,
+ struct symbol ***retained,
+ unsigned int *retained_cnt,
+ unsigned int *retained_allocated)
+{
+ down_write(&symbols->lock);
+ symbols__sort_locked(symbols);
+ *retained = symbols->symbols;
+ *retained_cnt = symbols->cnt;
+ *retained_allocated = symbols->allocated;
+ symbols->symbols = NULL;
+ symbols->cnt = 0;
+ symbols->sorted_cnt = 0;
+ symbols->allocated = 0;
+ zfree(&symbols->symbols_by_name);
+ symbols->num_by_name = 0;
+ symbols->sorted = true;
+ symbols->sorted_by_name = false;
+ up_write(&symbols->lock);
+}
+
+static void symbols__restore_retained(struct symbols *symbols,
+ struct symbol **retained,
+ unsigned int retained_cnt,
+ unsigned int retained_allocated)
+{
+ down_write(&symbols->lock);
+ symbols__delete_locked(symbols);
+ symbols->symbols = retained;
+ symbols->cnt = retained_cnt;
+ symbols->sorted_cnt = retained_cnt;
+ symbols->allocated = retained_allocated;
+ symbols->sorted = true;
+ symbols__sort_locked(symbols);
+ atomic_store_explicit(&symbols->shrunk, false, memory_order_release);
+ up_write(&symbols->lock);
+}
+
+static void symbols__merge_retained(struct symbols *symbols,
+ struct symbol **retained,
+ unsigned int retained_cnt)
+{
+ unsigned int fresh_cnt;
+ bool need_sort = false;
+
+ if (!retained)
+ return;
+
+ down_write(&symbols->lock);
+ symbols__sort_locked(symbols);
+ fresh_cnt = symbols->cnt;
+
+ for (unsigned int i = 0; i < retained_cnt; i++) {
+ struct symbol *ret_sym = retained[i];
+ u64 start = symbol__start(ret_sym);
+ unsigned int low = 0, high = fresh_cnt;
+ unsigned int match_idx = fresh_cnt;
+
+ while (low < high) {
+ unsigned int mid = low + (high - low) / 2;
+
+ if (symbol__start(symbols->symbols[mid]) < start)
+ low = mid + 1;
+ else
+ high = mid;
+ }
+
+ for (unsigned int k = low;
+ k < fresh_cnt && symbol__start(symbols->symbols[k]) == start;
+ k++) {
+ bool already_retained = false;
+
+ if (strcmp(symbol__name(symbols->symbols[k]),
+ symbol__name(ret_sym)) != 0)
+ continue;
+
+ for (unsigned int j = 0; j < i; j++) {
+ if (RC_CHK_EQUAL(symbols->symbols[k], retained[j])) {
+ already_retained = true;
+ break;
+ }
+ }
+ if (!already_retained) {
+ match_idx = k;
+ break;
+ }
+ }
+
+ if (match_idx < fresh_cnt) {
+ struct symbol *fresh_sym = symbols->symbols[match_idx];
+
+ symbol__set_end(ret_sym, symbol__end(fresh_sym));
+ if (symbol__ifunc_alias(fresh_sym))
+ symbol__set_ifunc_alias(ret_sym, true);
+ symbols->symbols[match_idx] = ret_sym;
+ symbol__put(fresh_sym);
+ } else if (__symbols__insert(symbols, ret_sym) == 0) {
+ need_sort = true;
+ }
+ }
+
+ if (need_sort)
+ symbols__sort_locked(symbols);
+
+ zfree(&symbols->symbols_by_name);
+ symbols->num_by_name = 0;
+ symbols->sorted_by_name = false;
+ atomic_store_explicit(&symbols->shrunk, false, memory_order_release);
+ up_write(&symbols->lock);
+ free(retained);
+}
+
static int __symbols__insert_sorted(struct symbols *symbols, struct symbol *sym)
{
unsigned int idx;
@@ -1449,6 +1669,7 @@ static int maps__split_kallsyms_for_kcore(struct maps *kmaps, struct dso *dso)
zfree(&root->symbols_by_name);
root->num_by_name = 0;
root->sorted_by_name = false;
+ atomic_store_explicit(&root->shrunk, false, memory_order_release);
up_write(&root->lock);
for (unsigned int i = 0; i < old_cnt; i++) {
@@ -1679,6 +1900,7 @@ static int maps__split_kallsyms(struct maps *kmaps, struct dso *dso, u64 delta,
zfree(&root->symbols_by_name);
root->num_by_name = 0;
root->sorted_by_name = false;
+ atomic_store_explicit(&root->shrunk, false, memory_order_release);
up_write(&root->lock);
free(old_symbols);
@@ -1711,6 +1933,7 @@ static int maps__split_kallsyms(struct maps *kmaps, struct dso *dso, u64 delta,
zfree(&root->symbols_by_name);
root->num_by_name = 0;
root->sorted_by_name = false;
+ atomic_store_explicit(&root->shrunk, false, memory_order_release);
up_write(&root->lock);
free(old_symbols);
return -1;
@@ -2388,6 +2611,11 @@ int dso__load(struct dso *dso, struct map *map)
struct symsrc *syms_ss = NULL, *runtime_ss = NULL;
bool kmod;
bool perfmap;
+ bool was_shrunk;
+ bool saved_adjust_symbols;
+ struct symbol **retained = NULL;
+ unsigned int retained_cnt = 0;
+ unsigned int retained_allocated = 0;
struct nscookie nsc;
char newmapname[PATH_MAX];
const char *map_path = dso__long_name(dso);
@@ -2408,8 +2636,15 @@ int dso__load(struct dso *dso, struct map *map)
/* check again under the dso->lock */
if (dso__loaded(dso)) {
ret = 1;
- goto out;
+ goto out_unlock;
}
+ was_shrunk = dso__symbols_shrunk(dso);
+ saved_adjust_symbols = dso__adjust_symbols(dso);
+ if (was_shrunk) {
+ symbols__detach_retained(dso__symbols(dso), &retained,
+ &retained_cnt, &retained_allocated);
+ }
+ atomic_store_explicit(&RC_CHK_ACCESS(dso)->loaded, false, memory_order_release);
kmod = dso__is_kmod(dso);
@@ -2556,6 +2791,8 @@ int dso__load(struct dso *dso, struct map *map)
nr_plt = dso__synthesize_plt_symbols(dso, runtime_ss);
if (nr_plt > 0)
ret += nr_plt;
+ if (nr_plt > 0 || !dso__symbols(dso)->sorted)
+ symbols__fixup_duplicate(dso__symbols(dso));
}
out_destroy:
@@ -2566,7 +2803,22 @@ int dso__load(struct dso *dso, struct map *map)
if (ret < 0 && strstr(dso__name(dso), " (deleted)") != NULL)
ret = 0;
out:
- dso__set_loaded(dso);
+ if (was_shrunk) {
+ if (ret > 0) {
+ symbols__merge_retained(dso__symbols(dso), retained, retained_cnt);
+ } else {
+ dso__set_adjust_symbols(dso, saved_adjust_symbols);
+ symbols__restore_retained(dso__symbols(dso), retained,
+ retained_cnt, retained_allocated);
+ if (retained_cnt > 0)
+ ret = retained_cnt;
+ }
+ dso__set_symbols_shrunk(dso, false);
+ dso__set_loaded(dso);
+ } else {
+ dso__set_loaded(dso);
+ }
+out_unlock:
mutex_unlock(dso__lock(dso));
nsinfo__mountns_exit(&nsc);
diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h
index 066697d8da8f..7d6469d6a976 100644
--- a/tools/perf/util/symbol.h
+++ b/tools/perf/util/symbol.h
@@ -135,12 +135,18 @@ struct symbols {
* by name.
*/
bool sorted_by_name;
+ /**
+ * @shrunk: True when symbols have been evicted by symbols__shrink()
+ * and may need to be reloaded on demand.
+ */
+ _Atomic bool shrunk;
};
void symbol__delete(struct symbol *sym);
void symbols__init(struct symbols *symbols);
void symbols__exit(struct symbols *symbols);
void symbols__delete(struct symbols *symbols);
+size_t symbols__shrink(struct symbols *symbols);
void symbols__sort_read_lock(struct symbols *symbols) SHARED_LOCK_FUNCTION(symbols->lock);
static inline refcount_t *symbol__refcnt(struct symbol *sym)
--
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 ` [PATCH v2 11/18] perf symbol-elf: Cache ELF program/section headers and defer symbol name lookup Ian Rogers
2026-10-30 13:32 ` 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 ` Ian Rogers [this message]
2026-10-30 13:53 ` [PATCH v2 15/18] perf symbol: Add memory shrinking for symbols, DSOs, and machines 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-16-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.