mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Ian Rogers <irogers@google.com>
To: Peter Zijlstra <peterz@infradead.org>,
	Ingo Molnar <mingo@redhat.com>,
	 Arnaldo Carvalho de Melo <acme@kernel.org>,
	Namhyung Kim <namhyung@kernel.org>, Jiri Olsa <jolsa@kernel.org>,
	 Ian Rogers <irogers@google.com>,
	Adrian Hunter <adrian.hunter@intel.com>,
	 James Clark <james.clark@linaro.org>,
	linux-perf-users@vger.kernel.org,  linux-kernel@vger.kernel.org,
	Alireza Haghdoost <haghdoost@uber.com>
Subject: [PATCH v1 5/7] perf symbol: Add LRU memory shrinking for symbols, DSOs, and machines
Date: Mon, 28 Sep 2026 00:52:35 -0700	[thread overview]
Message-ID: <20260928075237.3055101-6-irogers@google.com> (raw)
In-Reply-To: <20260928075237.3055101-1-irogers@google.com>

Add Clock/Second-Chance LRU access tracking (SYMBOL_FLAG_ACCESSED on
struct symbol, accessed on struct dso, and shrunk on struct symbols) and
memory-shrinking helpers (symbols__shrink, dso__can_shrink_symbols,
dso__shrink, dsos__shrink, machine__shrink, machines__shrink, and
perf_session__shrink):

- symbols__shrink() scans a symbol table under symbols->lock: symbols
  with refcnt > 1 are retained, symbols with SYMBOL_FLAG_ACCESSED set
  have their accessed bit cleared (second chance), and unreferenced
  symbols with SYMBOL_FLAG_ACCESSED clear are evicted. When symbols are
  evicted, symbols->shrunk is set under symbols->lock and the backing
  array is compacted when occupancy drops below 50%.
- dso__shrink() closes any cached file descriptor, frees cached 4KB DSO
  data pages (__dso_cache__free()) and addr2line/libbfd/libdw state
  under dso__lock(dso), and shrinks dso->symbols if the DSO supports
  idempotent on-demand reloading (dso__can_shrink_symbols()). Protect
  dso_cache lookup and memcpy under dso__lock(dso) so concurrent
  shrinking cannot free a cache page while it is being read.
- dsos__shrink() sweeps a DSO collection using Clock/Second-Chance LRU:
  accessed DSOs have their symbol tables shrunk and their accessed bit
  cleared; unaccessed DSOs are fully shrunk via dso__shrink() and
  evicted from struct dsos if unreferenced outside the collection
  (refcnt == 1) and free of unreloadable state.
- When a DSO's symbol table has been shrunk (dso__symbols_shrunk(dso) is
  true) and a subsequent symbol lookup misses in map__find_symbol() or
  map__find_symbol_by_name_idx(), transparently reload the DSO on demand
  while preferring actively referenced (refcnt > 1) symbols in
  choose_best_symbol() and deduplicating identical (start, name) pairs
  in symbols__fixup_duplicate() even when symbol_conf.allow_aliases is
  enabled.

Assisted-by: Antigravity:gemini-3.1-pro
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/dso.c        | 182 +++++++++++++++++++++++++++--------
 tools/perf/util/dso.h        |  46 +++++++++
 tools/perf/util/dsos.c       | 100 +++++++++++++++++++
 tools/perf/util/dsos.h       |  12 ++-
 tools/perf/util/machine.c    |  39 ++++++++
 tools/perf/util/machine.h    |   2 +
 tools/perf/util/map.c        |  26 ++++-
 tools/perf/util/session.c    |  15 +++
 tools/perf/util/session.h    |   3 +
 tools/perf/util/symbol-elf.c |   5 +-
 tools/perf/util/symbol.c     | 142 +++++++++++++++++++++++++--
 tools/perf/util/symbol.h     |  36 +++++--
 12 files changed, 551 insertions(+), 57 deletions(-)

diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c
index 1b96853164be..330e43cc325b 100644
--- a/tools/perf/util/dso.c
+++ b/tools/perf/util/dso.c
@@ -877,6 +877,7 @@ bool dso__data_get_fd(struct dso *dso, struct machine *machine, int *fd)
 	if (dso__data(dso)->status == DSO_DATA_STATUS_ERROR)
 		return false;
 
+	dso__set_accessed(dso, true);
 	mutex_lock(dso__data_open_lock());
 
 	try_to_open_dso(dso, machine);
@@ -955,12 +956,11 @@ 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)
 {
 	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;
 
@@ -969,6 +969,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));
 }
 
@@ -998,7 +1005,7 @@ static struct dso_cache *__dso_cache__find(struct dso *dso, u64 offset)
 }
 
 static struct dso_cache *
-dso_cache__insert(struct dso *dso, struct dso_cache *new)
+__dso_cache__insert(struct dso *dso, struct dso_cache *new)
 {
 	struct rb_root *root = &dso__data(dso)->cache;
 	struct rb_node **p = &root->rb_node;
@@ -1006,7 +1013,6 @@ dso_cache__insert(struct dso *dso, struct dso_cache *new)
 	struct dso_cache *cache;
 	u64 offset = new->offset;
 
-	mutex_lock(dso__lock(dso));
 	while (*p != NULL) {
 		u64 end;
 
@@ -1019,16 +1025,13 @@ dso_cache__insert(struct dso *dso, struct dso_cache *new)
 		else if (offset >= end)
 			p = &(*p)->rb_right;
 		else
-			goto out;
+			return cache;
 	}
 
 	rb_link_node(&new->rb_node, parent, p);
 	rb_insert_color(&new->rb_node, root);
 
-	cache = NULL;
-out:
-	mutex_unlock(dso__lock(dso));
-	return cache;
+	return NULL;
 }
 
 static ssize_t dso_cache__memcpy(struct dso_cache *cache, u64 offset, u8 *data,
@@ -1082,68 +1085,69 @@ static ssize_t file_read(struct dso *dso, struct machine *machine,
 	return ret;
 }
 
-static struct dso_cache *dso_cache__populate(struct dso *dso,
-					     struct machine *machine,
-					     u64 offset, ssize_t *ret)
+static ssize_t dso_cache__populate(struct dso *dso, struct machine *machine,
+				   u64 offset, u8 *data, ssize_t size, bool out)
 {
 	u64 cache_offset = offset & DSO__DATA_CACHE_MASK;
 	struct dso_cache *cache;
 	struct dso_cache *old;
+	ssize_t ret;
 
 	cache = zalloc(sizeof(*cache) + DSO__DATA_CACHE_SIZE);
-	if (!cache) {
-		*ret = -ENOMEM;
-		return NULL;
-	}
+	if (!cache)
+		return -ENOMEM;
 #ifdef HAVE_LIBBPF_SUPPORT
 	if (dso__binary_type(dso) == DSO_BINARY_TYPE__BPF_PROG_INFO)
-		*ret = bpf_read(dso, cache_offset, cache->data);
+		ret = bpf_read(dso, cache_offset, cache->data);
 	else
 #endif
 	if (dso__binary_type(dso) == DSO_BINARY_TYPE__OOL)
-		*ret = DSO__DATA_CACHE_SIZE;
+		ret = DSO__DATA_CACHE_SIZE;
 	else
-		*ret = file_read(dso, machine, cache_offset, cache->data);
+		ret = file_read(dso, machine, cache_offset, cache->data);
 
-	if (*ret <= 0) {
+	if (ret <= 0) {
 		free(cache);
-		return NULL;
+		return ret;
 	}
 
 	cache->offset = cache_offset;
-	cache->size   = *ret;
+	cache->size   = ret;
 
-	old = dso_cache__insert(dso, cache);
+	mutex_lock(dso__lock(dso));
+	old = __dso_cache__insert(dso, cache);
 	if (old) {
 		/* we lose the race */
 		free(cache);
 		cache = old;
 	}
+	ret = dso_cache__memcpy(cache, offset, data, size, out);
+	mutex_unlock(dso__lock(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);
+	return ret;
 }
 
+/*
+ * Look up and copy cached DSO data while holding dso__lock(dso) so a
+ * concurrent dso__shrink() / __dso_cache__free() cannot free the cache node
+ * while it is being read.
+ */
 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;
+	ssize_t ret;
 
-	cache = dso_cache__find(dso, machine, offset, &ret);
-	if (!cache)
+	mutex_lock(dso__lock(dso));
+	cache = __dso_cache__find(dso, offset);
+	if (cache) {
+		ret = dso_cache__memcpy(cache, offset, data, size, out);
+		mutex_unlock(dso__lock(dso));
 		return ret;
+	}
+	mutex_unlock(dso__lock(dso));
 
-	return dso_cache__memcpy(cache, offset, data, size, out);
+	return dso_cache__populate(dso, machine, offset, data, size, out);
 }
 
 /*
@@ -1157,6 +1161,7 @@ static ssize_t cached_io(struct dso *dso, struct machine *machine,
 	ssize_t r = 0;
 	u8 *p = data;
 
+	dso__set_accessed(dso, true);
 	do {
 		ssize_t ret;
 
@@ -1723,6 +1728,7 @@ struct dso *dso__new_id(const char *name, const struct dso_id *id)
 		dso->is_kmod = 0;
 		dso->needs_swap = DSO_SWAP__UNSET;
 		dso->comp = COMP_ID__NONE;
+		atomic_init(&dso->accessed, false);
 		mutex_init(&dso->lock);
 		refcount_set(&dso->refcnt, 1);
 		data = &dso->data;
@@ -1776,6 +1782,106 @@ void dso__delete(struct dso *dso)
 	RC_CHK_FREE(dso);
 }
 
+/**
+ * dso__can_shrink_symbols - Check if a DSO's symbol table can be shrunk and
+ *                           reloaded on demand.
+ * @dso: DSO to check.
+ *
+ * Shrinking evicts unreferenced symbols from @dso->symbols and relies on
+ * dso__load() being able to reload the missing symbols idempotently if a future
+ * lookup misses in map__find_symbol(). Not all DSO types support idempotent
+ * reloading:
+ *
+ * - User-space ELF binaries, debuginfo files, build-id cache files, and
+ *   perf-<pid>.map JIT files (dso__kernel(dso) == DSO_SPACE__USER), as well as
+ *   standalone kernel module ELF files (dso__is_kmod(dso)), load their symbols
+ *   directly into @dso->symbols from a backing file on disk and can be safely
+ *   reloaded on demand.
+ *
+ * - Main kernel and guest kernel DSOs (vmlinux / kallsyms where
+ *   dso__kernel(dso) != DSO_SPACE__USER && !dso__is_kmod(dso)) split their
+ *   loaded symbols across multiple per-section or per-module maps and DSOs in
+ *   machine->kmaps (via maps__split_kallsyms()). Re-running dso__load() on one
+ *   of the split kernel DSOs would either fail to find a backing file or
+ *   re-split and duplicate kernel maps, so they are excluded.
+ *
+ * - KCORE / GUEST_KCORE DSOs replace and remap kernel maps in machine->kmaps
+ *   when loaded from /proc/kcore, so they cannot be reloaded in place.
+ *
+ * - BPF_PROG_INFO, BPF_IMAGE, and OOL DSOs have their symbols synthesized in
+ *   memory from PERF_RECORD_KSYMBOL / PERF_RECORD_BPF_EVENT meta-events rather
+ *   than loaded from disk by dso__load(), so evicting their symbols would lose
+ *   them permanently.
+ *
+ * Return: True if @dso->symbols can be safely shrunk and reloaded on demand.
+ */
+bool dso__can_shrink_symbols(const struct dso *dso)
+{
+	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__JAVA_JIT:
+	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__SYSTEM_PATH_DSO:
+	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__OPENEMBEDDED_DEBUGINFO:
+	case DSO_BINARY_TYPE__GNU_DEBUGDATA:
+		return dso__kernel(dso) == DSO_SPACE__USER || dso__is_kmod(dso);
+	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__NOT_FOUND:
+	default:
+		return false;
+	}
+}
+
+/**
+ * dso__shrink - Reclaim cached file data, debuginfo, and unreferenced symbols
+ *               from a DSO.
+ * @dso: DSO to shrink.
+ *
+ * Closes any cached file descriptor, frees the 4KB page cache (@dso->data.cache),
+ * releases cached addr2line / libbfd / libdw handles, and shrinks @dso->symbols
+ * if supported by dso__can_shrink_symbols().
+ *
+ * Return: Number of symbols evicted from @dso->symbols.
+ */
+size_t dso__shrink(struct dso *dso)
+{
+	size_t removed = 0;
+
+	if (!dso)
+		return 0;
+
+	dso__data_close(dso);
+
+	mutex_lock(dso__lock(dso));
+	__dso_cache__free(dso);
+	dso__free_a2l(dso);
+	dso__free_a2l_libbfd(dso);
+	dso__free_libdw(dso);
+
+	if (dso__can_shrink_symbols(dso))
+		removed = symbols__shrink(dso__symbols(dso));
+	mutex_unlock(dso__lock(dso));
+
+	return removed;
+}
+
 struct dso *dso__get(struct dso *dso)
 {
 	struct dso *result;
diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h
index 1de446966f01..3f27cb1a76df 100644
--- a/tools/perf/util/dso.h
+++ b/tools/perf/util/dso.h
@@ -285,13 +285,24 @@ struct dso_bpf_prog {
 
 struct auxtrace_cache;
 
+/**
+ * struct dso - Dynamic Shared Object (executable, shared library, kernel, or
+ *              module) metadata, symbol table, and cached file state.
+ */
 DECLARE_RC_STRUCT(dso) {
+	/** @lock: Mutex serializing symbol loading and DSO state updates. */
 	struct mutex	 lock;
+	/** @dsos: Owning collection of DSOs, or NULL if not in a struct dsos. */
 	struct dsos	 *dsos;
+	/** @symbols: Sorted array of symbols belonging to this DSO. */
 	struct symbols	 symbols;
+	/** @inlined_nodes: Cached DWARF inline call-tree nodes for addresses. */
 	struct rb_root_cached inlined_nodes;
+	/** @srclines: Cached address-to-source-line mappings. */
 	struct rb_root_cached srclines;
+	/** @data_types: Cached DWARF annotated data type definitions. */
 	struct rb_root	 data_types;
+	/** @global_vars: Cached DWARF global variable locations. */
 	struct rb_root	 global_vars;
 
 	u64		 text_offset;
@@ -319,6 +330,7 @@ DECLARE_RC_STRUCT(dso) {
 	struct dso_id	 id;
 	unsigned int	 a2l_fails;
 	int		 comp;
+	/** @refcnt: Reference count tracking active holders of the DSO. */
 	refcount_t	 refcnt;
 	enum dso_load_errno	load_errno;
 	u16		 long_name_len;
@@ -338,6 +350,12 @@ DECLARE_RC_STRUCT(dso) {
 	u8		 short_name_allocated:1;
 	u8		 long_name_allocated:1;
 	u8		 is_64_bit:1;
+	/**
+	 * @accessed: Clock/Second-Chance LRU flag set when the DSO or its
+	 * symbols/data are looked up or read, and cleared by dsos__shrink().
+	 */
+	_Atomic bool	 accessed;
+	/** @loaded: True once dso__load() has attempted to load symbols. */
 	bool		 loaded;
 	u8		 rel;
 	char		 name[];
@@ -589,9 +607,30 @@ static inline enum dso_load_errno *dso__load_errno(struct dso *dso)
 	return &RC_CHK_ACCESS(dso)->load_errno;
 }
 
+static inline bool dso__accessed(const struct dso *dso)
+{
+	return atomic_load_explicit(&RC_CHK_ACCESS(dso)->accessed, memory_order_relaxed);
+}
+
+static inline void dso__set_accessed(struct dso *dso, bool val)
+{
+	atomic_store_explicit(&RC_CHK_ACCESS(dso)->accessed, val, memory_order_relaxed);
+}
+
+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 void dso__set_loaded(struct dso *dso)
 {
 	RC_CHK_ACCESS(dso)->loaded = true;
+	dso__set_symbols_shrunk(dso, false);
 }
 
 static inline struct mutex *dso__lock(struct dso *dso)
@@ -661,6 +700,11 @@ static inline void dso__set_rel(struct dso *dso, u8 rel)
 	RC_CHK_ACCESS(dso)->rel = rel;
 }
 
+static inline refcount_t *dso__refcnt(struct dso *dso)
+{
+	return &RC_CHK_ACCESS(dso)->refcnt;
+}
+
 static inline const char *dso__short_name(const struct dso *dso)
 {
 	return RC_CHK_ACCESS(dso)->short_name;
@@ -756,6 +800,8 @@ static inline void dso__set_text_offset(struct dso *dso, u64 val)
 struct dso *dso__new_id(const char *name, const struct dso_id *id);
 struct dso *dso__new(const char *name);
 void dso__delete(struct dso *dso);
+bool dso__can_shrink_symbols(const struct dso *dso);
+size_t dso__shrink(struct dso *dso);
 
 int dso__cmp_id(struct dso *a, struct dso *b);
 void dso__set_short_name(struct dso *dso, const char *name, bool name_allocated);
diff --git a/tools/perf/util/dsos.c b/tools/perf/util/dsos.c
index e927e707abac..f64551ec2728 100644
--- a/tools/perf/util/dsos.c
+++ b/tools/perf/util/dsos.c
@@ -278,9 +278,13 @@ static struct dso *__dsos__find_id(struct dsos *dsos, const char *name, const st
 		};
 
 		__dsos__for_each_dso(dsos, dsos__find_id_cb, &args);
+		if (args.res)
+			dso__set_accessed(args.res, true);
 		return args.res;
 	}
 	res = __dsos__find_by_longname_id(dsos, name, id, write_locked);
+	if (res)
+		dso__set_accessed(res, true);
 	return res;
 }
 
@@ -324,6 +328,7 @@ static struct dso *__dsos__addnew_id(struct dsos *dsos, const char *name, const
 		 * the array isn't sorted.
 		 */
 		dso__set_basename(dso);
+		dso__set_accessed(dso, true);
 		__dsos__add(dsos, dso);
 	}
 	return dso;
@@ -446,6 +451,7 @@ struct dso *dsos__findnew_module_dso(struct dsos *dsos,
 	dso__set_module_info(dso, m, machine);
 	dso__set_long_name(dso,	strdup(filename), true);
 	dso__set_kernel(dso, DSO_SPACE__KERNEL);
+	dso__set_accessed(dso, true);
 	__dsos__add(dsos, dso);
 
 	up_write(&dsos->lock);
@@ -493,3 +499,97 @@ int dsos__for_each_dso(struct dsos *dsos, int (*cb)(struct dso *dso, void *data)
 	up_read(&dsos->lock);
 	return err;
 }
+
+/**
+ * dsos__shrink - Reclaim cold symbols, DSO caches, and unreferenced DSOs.
+ * @dsos: Collection of DSOs to shrink.
+ *
+ * Implements a two-tier Clock/Second-Chance LRU eviction pass:
+ * 1. Snapshot all DSOs under the read lock and release @dsos->lock to respect
+ *    the lock ordering (dso__lock() -> dsos->lock).
+ *    - If a DSO was accessed since the previous shrink pass (dso__accessed(dso)
+ *      is true), its file-level caches (fd, data cache, srclines, inlines,
+ *      addr2line/libdw handles) get a second chance, while its unreferenced
+ *      cold symbols are still shrunk via symbols__shrink().
+ *    - If a DSO was not accessed since the previous shrink pass, dso__shrink()
+ *      is called to release its file descriptor, data cache, debuginfo caches,
+ *      and unreferenced symbols.
+ * 2. Re-acquire @dsos->lock for writing:
+ *    - Clear @dso->accessed on DSOs that were accessed during the interval.
+ *    - Evict and release cold user-space DSOs that have no remaining symbols,
+ *      no header build-id, and refcount == 1 (referenced only by @dsos->dsos).
+ *
+ * Return: Number of DSOs evicted from @dsos.
+ */
+size_t dsos__shrink(struct dsos *dsos)
+{
+	struct dso **snapshot = NULL;
+	unsigned int cnt = 0, dst = 0;
+	size_t removed = 0;
+
+	if (!dsos)
+		return 0;
+
+	down_read(&dsos->lock);
+	if (dsos->cnt > 0) {
+		snapshot = calloc(dsos->cnt, sizeof(*snapshot));
+		if (snapshot) {
+			cnt = dsos->cnt;
+			for (unsigned int i = 0; i < cnt; i++)
+				snapshot[i] = dso__get(dsos->dsos[i]);
+		}
+	}
+	up_read(&dsos->lock);
+
+	if (!snapshot)
+		return 0;
+
+	for (unsigned int i = 0; i < cnt; i++) {
+		struct dso *dso = snapshot[i];
+
+		if (dso__accessed(dso)) {
+			mutex_lock(dso__lock(dso));
+			if (dso__can_shrink_symbols(dso))
+				symbols__shrink(dso__symbols(dso));
+			mutex_unlock(dso__lock(dso));
+		} else {
+			dso__shrink(dso);
+		}
+		dso__put(dso);
+	}
+	free(snapshot);
+
+	down_write(&dsos->lock);
+	for (unsigned int src = 0; src < dsos->cnt; src++) {
+		struct dso *dso = dsos->dsos[src];
+
+		if (dso__accessed(dso)) {
+			dso__set_accessed(dso, false);
+			dsos->dsos[dst++] = dso;
+		} else if (!dso__has_symbols(dso) &&
+			   !dso__header_build_id(dso) &&
+			   !dso__kernel(dso) &&
+			   refcount_read(dso__refcnt(dso)) == 1) {
+			dso__set_dsos(dso, NULL);
+			dso__put(dso);
+			removed++;
+		} else {
+			dsos->dsos[dst++] = dso;
+		}
+	}
+	dsos->cnt = dst;
+	if (dst == 0) {
+		zfree(&dsos->dsos);
+		dsos->allocated = 0;
+	} else if (dst < dsos->allocated / 2) {
+		struct dso **temp = realloc(dsos->dsos, sizeof(*temp) * dst);
+
+		if (temp) {
+			dsos->dsos = temp;
+			dsos->allocated = dst;
+		}
+	}
+	up_write(&dsos->lock);
+
+	return removed;
+}
diff --git a/tools/perf/util/dsos.h b/tools/perf/util/dsos.h
index a26774950866..5c27f5efb74b 100644
--- a/tools/perf/util/dsos.h
+++ b/tools/perf/util/dsos.h
@@ -13,15 +13,20 @@ struct dso_id;
 struct kmod_path;
 struct machine;
 
-/*
- * Collection of DSOs as an array for iteration speed, but sorted for O(n)
- * lookup.
+/**
+ * struct dsos - Collection of DSOs stored as a sorted array for iteration speed
+ *               and O(log n) lookup.
  */
 struct dsos {
+	/** @lock: Read/write semaphore synchronizing access to @dsos. */
 	struct rw_semaphore lock;
+	/** @dsos: Dynamically allocated array of struct dso pointers. */
 	struct dso **dsos;
+	/** @cnt: Number of valid entries in @dsos. */
 	unsigned int cnt;
+	/** @allocated: Capacity (in entries) of the @dsos allocation. */
 	unsigned int allocated;
+	/** @sorted: True when @dsos is sorted by long name, dso_id, and short name. */
 	bool sorted;
 };
 
@@ -48,5 +53,6 @@ struct dso *dsos__findnew_module_dso(struct dsos *dsos, struct machine *machine,
 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 f1155c608a95..ba0b982828c2 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -962,6 +962,45 @@ size_t machines__fprintf_dsos_buildid(struct machines *machines, FILE *fp,
 	return ret;
 }
 
+/**
+ * machine__shrink - Reclaim unreferenced symbols, DSO caches, and cold DSOs
+ *                   for a machine.
+ * @machine: Machine to shrink.
+ *
+ * Return: Number of DSOs evicted from @machine->dsos.
+ */
+size_t machine__shrink(struct machine *machine)
+{
+	if (!machine)
+		return 0;
+
+	return dsos__shrink(&machine->dsos);
+}
+
+/**
+ * machines__shrink - Reclaim unreferenced symbols, DSO caches, and cold DSOs
+ *                    across the host and all guest machines.
+ * @machines: Collection of host and guest machines to shrink.
+ *
+ * Return: Total number of DSOs evicted across all machines.
+ */
+size_t machines__shrink(struct machines *machines)
+{
+	struct rb_node *nd;
+	size_t ret;
+
+	if (!machines)
+		return 0;
+
+	ret = machine__shrink(&machines->host);
+	for (nd = rb_first_cached(&machines->guests); nd; nd = rb_next(nd)) {
+		struct machine *pos = rb_entry(nd, struct machine, rb_node);
+
+		ret += machine__shrink(pos);
+	}
+	return ret;
+}
+
 struct machine_fprintf_cb_args {
 	FILE *fp;
 	size_t printed;
diff --git a/tools/perf/util/machine.h b/tools/perf/util/machine.h
index 973a59f7c26a..115e60979ec6 100644
--- a/tools/perf/util/machine.h
+++ b/tools/perf/util/machine.h
@@ -268,6 +268,8 @@ size_t machine__fprintf_dsos_buildid(struct machine *machine, FILE *fp,
 size_t machines__fprintf_dsos(struct machines *machines, FILE *fp);
 size_t machines__fprintf_dsos_buildid(struct machines *machines, FILE *fp,
 				     bool (skip)(struct dso *dso, int parm), int parm);
+size_t machine__shrink(struct machine *machine);
+size_t machines__shrink(struct machines *machines);
 
 void machine__destroy_kernel_maps(struct machine *machine);
 int machine__create_kernel_maps(struct machine *machine);
diff --git a/tools/perf/util/map.c b/tools/perf/util/map.c
index 19afe676adf2..633989615cf6 100644
--- a/tools/perf/util/map.c
+++ b/tools/perf/util/map.c
@@ -377,15 +377,30 @@ int map__load(struct map *map)
 
 struct symbol *map__find_symbol(struct map *map, u64 addr)
 {
+	struct dso *dso;
+	struct symbol *sym;
+
 	if (map__load(map) < 0)
 		return NULL;
 
-	return dso__find_symbol(map__dso(map), addr);
+	dso = map__dso(map);
+	sym = dso__find_symbol(dso, addr);
+	/*
+	 * If the DSO's symbol table was previously shrunk by symbols__shrink()
+	 * and the lookup missed, transparently reload the DSO so evicted
+	 * symbols are restored on demand rather than reported as [unknown].
+	 */
+	if (!sym && dso__symbols_shrunk(dso)) {
+		if (dso__load(dso, map) >= 0)
+			sym = dso__find_symbol(dso, addr);
+	}
+	return sym;
 }
 
 struct symbol *map__find_symbol_by_name_idx(struct map *map, const char *name, size_t *idx)
 {
 	struct dso *dso;
+	struct symbol *sym;
 
 	if (map__load(map) < 0)
 		return NULL;
@@ -393,7 +408,14 @@ struct symbol *map__find_symbol_by_name_idx(struct map *map, const char *name, s
 	dso = map__dso(map);
 	dso__sort_by_name(dso);
 
-	return dso__find_symbol_by_name(dso, name, idx);
+	sym = dso__find_symbol_by_name(dso, name, idx);
+	if (!sym && dso__symbols_shrunk(dso)) {
+		if (dso__load(dso, map) >= 0) {
+			dso__sort_by_name(dso);
+			sym = dso__find_symbol_by_name(dso, name, idx);
+		}
+	}
+	return sym;
 }
 
 struct symbol *map__find_symbol_by_name(struct map *map, const char *name)
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index 7fea9e72726c..555d940a866a 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -3933,6 +3933,21 @@ size_t perf_session__fprintf_dsos_buildid(struct perf_session *session, FILE *fp
 	return machines__fprintf_dsos_buildid(&session->machines, fp, skip, parm);
 }
 
+/**
+ * perf_session__shrink - Reclaim unreferenced symbols, DSO caches, and cold
+ *                        DSOs across all machines in the session.
+ * @session: Session to shrink.
+ *
+ * Return: Number of DSOs evicted across the session's machines.
+ */
+size_t perf_session__shrink(struct perf_session *session)
+{
+	if (!session)
+		return 0;
+
+	return machines__shrink(&session->machines);
+}
+
 size_t perf_session__fprintf_nr_events(struct perf_session *session, FILE *fp)
 {
 	size_t ret;
diff --git a/tools/perf/util/session.h b/tools/perf/util/session.h
index ac5803d5fb4e..4b223c2cee7f 100644
--- a/tools/perf/util/session.h
+++ b/tools/perf/util/session.h
@@ -91,7 +91,9 @@ struct perf_session {
 	u64			bytes_compressed;
 	/** @zstd_data: Owner of global compression state, buffers, etc. */
 	struct zstd_data	zstd_data;
+	/** @decomp_data: Decompression state for the session data file. */
 	struct decomp_data	decomp_data;
+	/** @active_decomp: Currently active decompression context. */
 	struct decomp_data	*active_decomp;
 };
 
@@ -175,6 +177,7 @@ size_t perf_session__fprintf_dsos(struct perf_session *session, FILE *fp);
 
 size_t perf_session__fprintf_dsos_buildid(struct perf_session *session, FILE *fp,
 					  bool (fn)(struct dso *dso, int parm), int parm);
+size_t perf_session__shrink(struct perf_session *session);
 
 size_t perf_session__fprintf_nr_events(struct perf_session *session, FILE *fp);
 
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
index 90542fb643e9..00d088b43f94 100644
--- a/tools/perf/util/symbol-elf.c
+++ b/tools/perf/util/symbol-elf.c
@@ -1826,9 +1826,10 @@ 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.
+	 * have the wrong values for the dso maps, so remove them unless we are
+	 * reloading a previously shrunk module.
 	 */
-	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 593fccfe3240..33cfd86156d2 100644
--- a/tools/perf/util/symbol.c
+++ b/tools/perf/util/symbol.c
@@ -164,6 +164,14 @@ static int choose_best_symbol(struct symbol *syma, struct symbol *symb)
 	s64 b;
 	size_t na, nb;
 
+	/* Prefer a symbol that is actively referenced */
+	a = refcount_read(symbol__refcnt(syma)) > 1;
+	b = refcount_read(symbol__refcnt(symb)) > 1;
+	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);
@@ -224,6 +232,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)
@@ -238,6 +247,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)
@@ -253,6 +263,68 @@ void symbols__exit(struct symbols *symbols)
 	exit_rwsem(&symbols->lock);
 }
 
+/**
+ * symbols__shrink - Evict unreferenced, unaccessed symbols using Clock LRU.
+ * @symbols: Symbol table to shrink.
+ *
+ * Scans @symbols in place:
+ * - Symbols with refcount > 1 (held by an external owner such as a hist_entry,
+ *   callchain_cursor_node, or inline_list) are always retained.
+ * - Symbols with refcount == 1 that have SYMBOL_FLAG_ACCESSED set are given a
+ *   second chance: their accessed bit is cleared and they remain in @symbols.
+ * - Symbols with refcount == 1 and SYMBOL_FLAG_ACCESSED clear are evicted and
+ *   released via symbol__put().
+ *
+ * If any symbols are evicted, the secondary @symbols_by_name index is freed so
+ * it will be rebuilt lazily if needed, and the backing @symbols array is
+ * compacted when occupancy drops below 50%.
+ *
+ * Return: Number of symbols evicted and freed.
+ */
+size_t symbols__shrink(struct symbols *symbols)
+{
+	unsigned int write_idx = 0;
+	size_t freed = 0;
+
+	if (!symbols)
+		return 0;
+
+	down_write(&symbols->lock);
+	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__accessed(sym)) {
+			symbol__set_accessed(sym, false);
+			symbols->symbols[write_idx++] = sym;
+		} else {
+			symbol__put(sym);
+			freed++;
+		}
+	}
+	if (freed > 0) {
+		symbols->cnt = write_idx;
+		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 (symbols->cnt == 0) {
+			zfree(&symbols->symbols);
+			symbols->allocated = 0;
+		} else if (symbols->cnt < symbols->allocated / 2 && symbols->allocated > 32) {
+			unsigned int new_alloc = max(symbols->cnt, 32U);
+			struct symbol **temp = realloc(symbols->symbols,
+						       sizeof(struct symbol *) * new_alloc);
+
+			if (temp) {
+				symbols->symbols = temp;
+				symbols->allocated = new_alloc;
+			}
+		}
+	}
+	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))
@@ -310,9 +382,6 @@ void symbols__fixup_duplicate(struct symbols *symbols)
 {
 	unsigned int write_idx = 0;
 
-	if (symbol_conf.allow_aliases)
-		return;
-
 	down_write(&symbols->lock);
 	symbols__sort_locked(symbols);
 	if (symbols->cnt <= 1) {
@@ -329,6 +398,38 @@ void symbols__fixup_duplicate(struct symbols *symbols)
 			continue;
 		}
 
+		/*
+		 * When aliases are allowed, distinct symbol names at the same
+		 * address are kept, but identical (start, name) pairs (such as
+		 * when reloading a shrunk DSO that still holds referenced
+		 * symbols) are deduplicated using choose_best_symbol().
+		 */
+		if (symbol_conf.allow_aliases) {
+			bool dup = false;
+
+			for (unsigned int k = write_idx; ; k--) {
+				struct symbol *existing = symbols->symbols[k];
+
+				if (symbol__start(existing) != symbol__start(next))
+					break;
+				if (!strcmp(symbol__name(existing), symbol__name(next))) {
+					if (choose_best_symbol(existing, next) == SYMBOL_A) {
+						symbol__put(next);
+					} else {
+						symbol__put(existing);
+						symbols->symbols[k] = next;
+					}
+					dup = true;
+					break;
+				}
+				if (k == 0)
+					break;
+			}
+			if (!dup)
+				symbols->symbols[++write_idx] = next;
+			continue;
+		}
+
 		if (choose_best_symbol(curr, next) == SYMBOL_A) {
 			if (symbol__type(next) == STT_GNU_IFUNC)
 				symbol__set_ifunc_alias(curr, true);
@@ -502,6 +603,14 @@ void symbol__set_annotated(struct symbol *sym, bool annotated)
 		atomic_fetch_and(&RC_CHK_ACCESS(sym)->flags, ~SYMBOL_FLAG_ANNOTATED);
 }
 
+void symbol__set_accessed(struct symbol *sym, bool accessed)
+{
+	if (accessed)
+		atomic_fetch_or(&RC_CHK_ACCESS(sym)->flags, SYMBOL_FLAG_ACCESSED);
+	else
+		atomic_fetch_and(&RC_CHK_ACCESS(sym)->flags, ~SYMBOL_FLAG_ACCESSED);
+}
+
 static void symbol__set_idle(struct symbol *sym, bool idle)
 {
 	uint16_t old_flags = atomic_load_explicit(&RC_CHK_ACCESS(sym)->flags, memory_order_relaxed);
@@ -576,6 +685,7 @@ static struct symbol *symbols__find(struct symbols *symbols, u64 ip)
 			low = mid + 1;
 		else {
 			res = symbol__get(s);
+			symbol__set_accessed(res, true);
 			break;
 		}
 	}
@@ -700,7 +810,11 @@ static struct symbol *symbols__find_by_name(struct symbol *symbols[],
 		}
 	}
 	assert(!found_idx || !s || s == symbols[*found_idx]);
-	return symbol__get(s);
+	if (s) {
+		s = symbol__get(s);
+		symbol__set_accessed(s, true);
+	}
+	return s;
 }
 
 int dso__insert_symbol(struct dso *dso, struct symbol *sym)
@@ -732,11 +846,17 @@ void dso__delete_symbol(struct dso *dso, struct symbol *sym)
 
 struct symbol *dso__find_symbol(struct dso *dso, u64 addr)
 {
+	if (!dso)
+		return NULL;
+	dso__set_accessed(dso, true);
 	return symbols__find(dso__symbols(dso), addr);
 }
 
 struct symbol *dso__find_symbol_nocache(struct dso *dso, u64 addr)
 {
+	if (!dso)
+		return NULL;
+	dso__set_accessed(dso, true);
 	return symbols__find(dso__symbols(dso), addr);
 }
 
@@ -763,6 +883,9 @@ struct symbol *dso__find_symbol_by_name(struct dso *dso, const char *name, size_
 	struct symbols *symbols;
 	struct symbol *s;
 
+	if (!dso)
+		return NULL;
+	dso__set_accessed(dso, true);
 	symbols = dso__symbols(dso);
 	down_read(&symbols->lock);
 	while (!symbols->sorted_by_name) {
@@ -1857,6 +1980,9 @@ static int dso__load_perf_map(const char *map_path, struct dso *dso)
 	free(line);
 	fclose(file);
 
+	if (nr_syms > 0)
+		symbols__fixup_duplicate(dso__symbols(dso));
+
 	return nr_syms;
 
 out_delete_line:
@@ -1967,6 +2093,7 @@ int dso__load(struct dso *dso, struct map *map)
 	char newmapname[PATH_MAX];
 	const char *map_path = dso__long_name(dso);
 
+	dso__set_accessed(dso, true);
 	mutex_lock(dso__lock(dso));
 	perfmap = is_perf_pid_map_name(map_path);
 
@@ -1981,7 +2108,7 @@ int dso__load(struct dso *dso, struct map *map)
 	nsinfo__mountns_enter(dso__nsinfo(dso), &nsc);
 
 	/* check again under the dso->lock */
-	if (dso__loaded(dso)) {
+	if (dso__loaded(dso) && !dso__symbols_shrunk(dso)) {
 		ret = 1;
 		goto out;
 	}
@@ -2140,8 +2267,10 @@ int dso__load(struct dso *dso, struct map *map)
 		int nr_plt;
 
 		nr_plt = dso__synthesize_plt_symbols(dso, runtime_ss);
-		if (nr_plt > 0)
+		if (nr_plt > 0) {
+			symbols__fixup_duplicate(dso__symbols(dso));
 			ret += nr_plt;
+		}
 	}
 
 	for (; ss_pos > 0; ss_pos--)
@@ -2151,6 +2280,7 @@ int dso__load(struct dso *dso, struct map *map)
 	if (ret < 0 && strstr(dso__name(dso), " (deleted)") != NULL)
 		ret = 0;
 out:
+	dso__set_symbols_shrunk(dso, false);
 	dso__set_loaded(dso);
 	mutex_unlock(dso__lock(dso));
 	nsinfo__mountns_exit(&nsc);
diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h
index a3c88b5224ec..a76613c43ba9 100644
--- a/tools/perf/util/symbol.h
+++ b/tools/perf/util/symbol.h
@@ -91,21 +91,32 @@ enum symbol_idle_kind {
 #define SYMBOL_FLAG_ANNOTATE2       (1 << 12)
 #define SYMBOL_FLAG_IFUNC_ALIAS     (1 << 13)
 #define SYMBOL_FLAG_ANNOTATED       (1 << 14)
+#define SYMBOL_FLAG_ACCESSED        (1U << 15)
+
 /**
- * A symtab entry.
+ * struct symbol - A symtab entry.
  */
 DECLARE_RC_STRUCT(symbol) {
-	/** Range of symbol [start, end). */
+	/** @start: Start address of symbol range [start, end). */
 	u64		start;
+	/** @end: End address (exclusive) of symbol range [start, end). */
 	u64		end;
-	/** Reference count. */
+	/** @refcnt: Reference count tracking active holders of the symbol. */
 	refcount_t	refcnt;
-	/** Length of the string name. */
+	/** @namelen: Length of the string @name (excluding trailing NUL). */
 	u16		namelen;
+	/**
+	 * @flags: Atomic bitfield of SYMBOL_FLAG_* values encoding symbol type,
+	 * binding, idle classification, ignore/inlined/annotate/ifunc flags,
+	 * and Clock LRU access state (SYMBOL_FLAG_ACCESSED).
+	 */
 	_Atomic uint16_t flags;
-	/** Architecture specific. Unused except on PPC where it holds st_other. */
+	/**
+	 * @arch_sym: Architecture specific value. Unused except on PPC where it
+	 * holds st_other.
+	 */
 	u8		arch_sym;
-	/** The name of length namelen associated with the symbol. */
+	/** @name: NUL-terminated symbol name of length @namelen. */
 	char		name[];
 };
 
@@ -138,6 +149,11 @@ struct symbols {
 	 * by name.
 	 */
 	bool sorted_by_name;
+	/**
+	 * @shrunk: Set under @lock when symbols__shrink() evicts one or more
+	 * symbols so a subsequent lookup miss knows to reload the DSO on demand.
+	 */
+	_Atomic bool shrunk;
 };
 
 void symbol__delete(struct symbol *sym);
@@ -145,6 +161,7 @@ void symbols__init(struct symbols *symbols);
 void symbols__exit(struct symbols *symbols);
 void symbols__delete(struct symbols *symbols);
 void symbols__sort_read_lock(struct symbols *symbols) SHARED_LOCK_FUNCTION(symbols->lock);
+size_t symbols__shrink(struct symbols *symbols);
 
 static inline refcount_t *symbol__refcnt(struct symbol *sym)
 {
@@ -253,6 +270,12 @@ static inline bool symbol__annotated(const struct symbol *sym)
 		SYMBOL_FLAG_ANNOTATED) != 0;
 }
 
+static inline bool symbol__accessed(const struct symbol *sym)
+{
+	return (atomic_load_explicit(&RC_CHK_ACCESS(sym)->flags, memory_order_relaxed) &
+		SYMBOL_FLAG_ACCESSED) != 0;
+}
+
 bool symbol__is_idle(struct symbol *sym, const struct dso *dso, struct perf_env *env);
 
 void symbol__set_ignore(struct symbol *sym, bool ignore);
@@ -260,6 +283,7 @@ void symbol__set_annotate2(struct symbol *sym, bool annotate2);
 void symbol__set_inlined(struct symbol *sym, bool inlined);
 void symbol__set_ifunc_alias(struct symbol *sym, bool ifunc_alias);
 void symbol__set_annotated(struct symbol *sym, bool annotated);
+void symbol__set_accessed(struct symbol *sym, bool accessed);
 
 /*
  * symbols__for_each_entry - iterate over symbols calling @cb for each entry
-- 
2.56.0.rc1.315.gc6ed9934b7-goog


  parent reply	other threads:[~2026-09-28  7:53 UTC|newest]

Thread overview: 10+ 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 ` Ian Rogers [this message]
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

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=20260928075237.3055101-6-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®