Skip to content

[pipeline] Keep the symbol inventory's analysis facts through other selections - #1328

Merged
gmh5225 merged 2 commits into
devfrom
claude/perf-next
Oct 18, 2026
Merged

gmh5225 merged 2 commits into
devfrom
claude/perf-next

Conversation

@gmh5225

@gmh5225 gmh5225 commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A selected function that is not a symbol start adds its own entry to its run's entry inventory, so its run keys different analysis facts. The session's PipelineAnalysisCache held one set of facts, so such a run replaced the facts that every symbol start shares and that warming builds. The next symbol start then replaced them again.

PE images hit this: the worker's listing finds functions that no image symbol starts. Decompiling every function in address order replaced the facts 21 times in a Rust Windows DLL (338 functions) and 17 times in MSVC xcpt4 (306). The warmed cache was gone by the DLL's 20th function. Each replacement made the following functions rebuild callee graphs that warming had built, which is why some tiny tail-calling functions took 85 ms. The Go and Mach-O lists measured had no such functions.

The cache now keeps the facts of a selection that adds entries in their own slot, beside the symbol inventory's:

  • Each run still uses only facts built for its exact inventory (matches is unchanged).
  • reset() retires both slots.
  • Warming replaces facts built for another symbol inventory instead of refusing. Such facts serve no later run, so neverd_warm_functions_json is now available on every x86-64 image.

The doc comment of PipelineAnalysisCache had drifted above AnalysisCacheWarmup (#1271) and is back on the class. The architecture guide and the warming API comment describe the new rule.

Measurements

Release build, NEVERD_THREADS=4, pinned to 4 CPUs. Each run decompiled every function in address order in one worker after warming. Runs alternated between the dev library (#1324) and this change.

Binary dev This change Differing sources
Rust Windows DLL (rust-eh/x86_64-pc-windows-msvc/abort/o2/cdylib, 338 functions), 3 runs 9.12 / 9.36 / 9.37 s 7.51 / 7.57 / 7.60 s 0
MSVC x64 xcpt4 FH3, GS, O2 (306), 3 runs 3.10 / 3.00 / 2.94 s 3.04 / 2.88 / 2.92 s 0
MSVC x64 xcpt4 FH3, no GS, O0 (420) 4.31 s 4.11 s 0

Peak RSS:

  • Rust DLL: 269–295 MB before, 265–279 MB after.
  • xcpt4 O2: 286–308 MB before, 319–336 MB after. The symbol inventory's cache is no longer discarded 17 times, so it grows to the size it reaches on images without such selections, within the cache's own bounds.
  • Fixed cost of a second facts set: its image indexes, a few MB for ntoskrnl.

Tests

  • New PipelineOutcome.SelectionsOutsideTheSymbolInventoryKeepTheirOwnFacts:
    • A non-symbol selection keys facts of its own and leaves the symbol inventory's in place.
    • The next symbol start reuses those facts, and every result equals a cold run.
    • Warming adds to them and leaves the selection's.
    • Facts for another image are replaced, and reset() retires both.
    • It replaces WarmingKeepsAnalysisKeptForOtherEntries, which pinned the old rule.
  • PipelineOutcome.AnalysisCacheBindsExactEntryInventories now checks both slots. A run builds new facts exactly when its own inventory changed, including after verified-entry changes, and leaves the other kind alone.
  • New SessionCAPITest.AnalysisCacheSurvivesSelectingAnEntryNoSymbolStarts: preparing padding that no symbol starts keeps the session's facts, and the first function reads back the same. The test asserts that no symbol starts there, because the session gives every call target a symbol.
  • Each new or restated test fails with the slot choice forced back to the single set of facts.
  • NeverDSessionCAPITests 165/165, NeverDPipelineOutcomeTests 42/42, NeverDX87CallGraphTests 57/57, NeverDNoReturnTests 37/37 and NeverDJumpTableTests 544/544 pass. So do the worker warming and PE tests with the real engine (4/4).
  • scripts/check_clang_format.py and scripts/check_docs_i18n.py pass.
  • After merging the latest dev: NeverDSessionCAPITests 167/167, NeverDPipelineOutcomeTests 42/42 and the worker tests 4/4 pass.

…elections

A selected function that is not a symbol start adds its own entry to its
run's inventory, which keys different analysis facts.  The session cache
held one set, so such a run replaced the facts every symbol start shares
and warming builds, and the next symbol start replaced them again.  PE
images hit this: the worker's listing finds functions no symbol starts,
and an address-order pass over a Rust DLL replaced the facts 21 times,
rebuilding callee graphs warming had built.

PipelineAnalysisCache keeps a selection that adds entries in its own slot
beside the symbol inventory's facts.  Each run still uses only facts built
for its exact inventory, and reset() retires both.  Warming replaces facts
for another symbol inventory, which serve no later run, instead of
refusing.  The class's doc comment, which AnalysisCacheWarmup had
separated from it, is back on the class.
@gmh5225
gmh5225 merged commit db54dd7 into dev Oct 18, 2026
6 of 22 checks passed
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.

2 participants