Skip to content

perf(debuginfo): sort DWARF line tables on packed (addr, index) keys - #38

Open
codspeed-hq[bot] wants to merge 1 commit into
masterfrom
codspeed-optim-sort-dwarf-line-tables-on-packed-addr-index-keys-i-1788231632551
Open

perf(debuginfo): sort DWARF line tables on packed (addr, index) keys#38
codspeed-hq[bot] wants to merge 1 commit into
masterfrom
codspeed-optim-sort-dwarf-line-tables-on-packed-addr-index-keys-i-1788231632551

Conversation

@codspeed-hq

@codspeed-hq codspeed-hq Bot commented Sep 1, 2026

Copy link
Copy Markdown

What

canonicaliseLoctab() sorts the DWARF line table (loctab) of every loaded object at startup. It did so by building an array of UInt indexes and handing it to the generic VG_(ssort), with a comparison function that dereferences di->loctab[ix] for both operands.

That makes every one of the O(n log n) comparisons:

  • an indirect call through a function pointer, and
  • two random accesses into a multi-megabyte 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 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 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-dbg installed so glibc's 137k-entry line table is loaded; the LFS bench fixtures are not available in the sandbox, so echo / ls / python3 were used with the repo's callgrind configs).

Two independent base/head pairs, the second with the run order reversed to guard against machine drift:

pair impact
base → head +1.4%
head → base (reversed order) +1.1%

All 12 benchmarks moved in the same direction in both pairs. Examples (best time, first pair):

benchmark before after
echo Hello, World!, inline 286.88 ms 278.54 ms
echo Hello, World!, no-inline 207.21 ms 202.60 ms
ls /usr/lib, cycle-estimation 251.39 ms 246.48 ms
python3 -c pass, full-with-inline 2.11 s 2.08 s

Directly 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

  • Callgrind regression suite (the suite CI runs): 22/22 pass, 0 failures.
  • Memcheck regression suite: 292 tests, the exact same 2 pre-existing failures (gone_abrt_xml, vcpu_bz2) as the unmodified build — no new failures.
  • Callgrind output for echo with --read-inline-info=yes has identical totals (summary/totals byte-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.

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.
@codspeed-hq

codspeed-hq Bot commented Sep 1, 2026

Copy link
Copy Markdown
Author

Merging this PR will not alter performance

✅ 84 untouched benchmarks
⏩ 60 skipped benchmarks1


Comparing codspeed-optim-sort-dwarf-line-tables-on-packed-addr-index-keys-i-1788231632551 (827000d) with master (48aa2c6)

Open in CodSpeed

Footnotes

  1. 60 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@codspeed-hq
codspeed-hq Bot marked this pull request as ready for review September 1, 2026 04:06
@greptile-apps

greptile-apps Bot commented Sep 1, 2026

Copy link
Copy Markdown

Greptile Summary

The 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.

  • Adds an inlined median-of-three quicksort with insertion sorting for small partitions and bounded recursion depth.
  • Builds packed address/index keys, derives the existing permutation array from them, and frees the temporary keys before permutation.
  • Orders equal addresses by original index so canonicalisation consistently retains the last emitted row.

Confidence Score: 5/5

The 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.

Important Files Changed

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]
Loading

Reviews (1): Last reviewed commit: "perf(debuginfo): sort loctab on packed (..." | Re-trigger Greptile

@codspeed-hq
codspeed-hq Bot requested a review from not-matthias September 1, 2026 04:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant