perf(debuginfo): sort DWARF line tables on packed (addr, index) keys - #38
Conversation
canonicaliseLoctab() sorted the DWARF line table of every loaded object by
handing an array of UInt indexes to VG_(ssort), whose comparison function
dereferenced di->loctab[ix] for both operands: an indirect call plus two
random accesses into a multi-megabyte table for every one of the O(n log n)
comparisons. VG_(ssort) also exchanges elements whose size is not a
multiple of the word size, such as these 4 byte indexes, one byte at a time.
Sort a packed { Addr addr; UInt ix; } key array with a small specialised
quicksort instead (median-of-3 pivot, insertion sort for short partitions,
recursion into the smaller side so the stack depth stays O(log n)). The
comparison is inlined, the key sits next to the index it moves, and swaps
are whole-struct moves. The index array and the in-place permutation that
follows are unchanged.
Ties are broken by the original index, so the ordering of entries sharing an
address is now deterministic instead of depending on quicksort's arbitrary
placement of equal elements.
Merging this PR will not alter performance
Comparing Footnotes
|
Greptile SummaryThe PR replaces indirect sorting of DWARF line-table indexes with a specialized sort over packed address/index keys, improving locality while preserving the existing in-place permutation. It also makes duplicate-address resolution deterministic by retaining the last original line-program row.
Confidence Score: 5/5The PR appears safe to merge with no actionable correctness or security issues identified. The specialized sort remains bounded, strictly reduces each partition, preserves every permutation index, and its ascending index tie-break works with canonicalisation to retain the intended last duplicate-address row.
|
| Filename | Overview |
|---|---|
| coregrind/m_debuginfo/storage.c | Replaces generic indirect loctab sorting with a bounded specialized key sort while preserving permutation and canonicalisation invariants. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[DWARF loctab rows] --> B[Build packed address/index keys]
B --> C[Sort by address then original index]
C --> D[Build permutation indexes]
D --> E[Permute loctab and filename indexes]
E --> F[Canonicalise overlaps and duplicate addresses]
F --> G[Binary-searchable source locations]
Reviews (1): Last reviewed commit: "perf(debuginfo): sort loctab on packed (..." | Re-trigger Greptile
What
canonicaliseLoctab()sorts the DWARF line table (loctab) of every loaded object at startup. It did so by building an array ofUIntindexes and handing it to the genericVG_(ssort), with a comparison function that dereferencesdi->loctab[ix]for both operands.That makes every one of the O(n log n) comparisons:
loctab— a guaranteed cache miss once the table no longer fits in L2.On top of that,
VG_(ssort)(Bentley–McIlroy quicksort,coregrind/m_libcbase.c) exchanges elements whose size is not a multiple of the word size — such as these 4-byte indexes — one byte at a time.The change
Sort a packed
{ Addr addr; UInt ix; }key array with a small specialised quicksort (median-of-3 pivot, insertion sort for short partitions, recursion into the smaller side so stack depth staysO(log n)). The comparison is inlined, the key sits next to the index it moves, and swaps are whole-struct moves. The index array and the in-place permutation that follows are unchanged.Ties are broken by the original index, so entries sharing an address are now ordered deterministically (last line-program row for an address wins) instead of depending on quicksort's arbitrary placement of equal elements.
Single file touched:
coregrind/m_debuginfo/storage.c(+83/−12).Measured impact
Verified locally by building the branch and its merge-base and benchmarking both with
codspeed run --mode walltime(sandbox x86_64 VM,libc6-dbginstalled so glibc's 137k-entry line table is loaded; the LFS bench fixtures are not available in the sandbox, soecho/ls/python3were used with the repo's callgrind configs).Two independent base/head pairs, the second with the run order reversed to guard against machine drift:
All 12 benchmarks moved in the same direction in both pairs. Examples (best time, first pair):
echo Hello, World!, inlineecho Hello, World!, no-inlinels /usr/lib, cycle-estimationpython3 -c pass, full-with-inlineDirectly instrumenting
sort_loctab_and_loctab_fndn_ix()(temporary build, not part of this PR) confirms the mechanism: for glibc's line table (137204 entries) the sort + permutation drops from ~10 ms to ~6–7 ms per process on this machine. Every Valgrind invocation pays this at startup, so the short commands benefit the most. The macro-runner numbers from CI should be cleaner than this sandbox's ~1–3% run-to-run noise.Correctness
gone_abrt_xml,vcpu_bz2) as the unmodified build — no new failures.echowith--read-inline-info=yeshas identical totals (summary/totalsbyte-identical) versus the baseline build. One function shows the same total cost redistributed across two adjacent lines: that is the tie-break change described above, where duplicate-address line entries now resolve deterministically instead of arbitrarily.