Re: [PATCH v2 15/18] perf symbol: Add memory shrinking for symbols, DSOs, and machines

From: Alireza Haghdoost

Date: Fri Oct 30 2026 - 09:54:23 EST


On Tue, Oct 27, 2026 at 4:15 PM Ian Rogers <irogers@xxxxxxxxxx> wrote:
> + 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);

Could the failed-reload path leave the DSO in a state where a later
lookup can retry, rather than clearing symbols_shrunk and marking it
loaded?

If the file is gone or no longer matches when the reload runs, this
restores only the retained symbols and still marks the DSO loaded and
not shrunk. The evicted symbols never come back and nothing retries.
For a DSO without a build-id whose file was replaced, the reload would
also merge the new file's symbols with the old retained ones.

Could the reload also build a local struct symbols and swap it in
under the write lock, instead of exposing the table while it is being
rebuilt?

dso__load() currently detaches the retained symbols for the whole
reload and merges them back at the end. map__find_symbol() waits for
the dso lock, but a direct dso__find_symbol() from another thread can
miss the retained symbols or get the fresh copy meanwhile. In a test
with one lookup thread and one shrink plus map__load() thread, 115,691
lookups missed a retained symbol, and most of the 13.2M lookups
returned the fresh copy instead of the retained object. That breaks
the pointer identity annotations rely on. Swapping a completed table
under the write lock would close that window.

Smaller things:

- symbols__merge_retained() is O(R^2) in retained symbols because of
the already_retained scan. Both arrays are sorted, so a linear merge
works.

- symbols__fixup_duplicate() no longer returns early for
allow_aliases, and dso__load() now runs a second dedupe after PLT
synthesis on every load, not only after a shrink.

- choose_best_symbol() now prefers refcount > 1, so which alias wins
depends on references other threads hold at that moment.

- If shrink removes a newly populated cache entry, dso_cache_io()
returns -EAGAIN. cached_io() does not retry, so this transient race
becomes a hard DSO read failure.

Thanks,
Alireza