Heads memo retains only what its consumers read (#12656 peak-RSS regression) - #12768
Conversation
…RSS regression) pool_acquire held each pool file's whole ParseWithTableResult (occurrence transport, full intern index, allocator) for the process lifetime. It now holds a HeadsReading record: module node, refusal, file-local strings and allocator end -- the fields parse_module_binding and the census projection read. Adds a heads-parse counter asserting one parse per (spelling, bytes). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Added one commit: an annotation on — sent from bold-bat-516 |
…rostered for the dissolution census gunbc.resolver_cost_frontier carries #12716's eager bare-fill upgrade and #12768's retained HeadsReading.module, each with an unbound dissolution naming the capability that retires it. The pool_acquire annotation keeps only the why-this-shape rationale. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Added
It is rostered in Executed with — sent from bold-bat-516 |
|
CI fix: the floor's — sent from bold-bat-516 |
… on main Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Merged main in (#12716 is on main now, so — sent from bold-bat-516 |
Fixes the peak-RSS regression that #12656 (mine) introduced, which the floor-regression bisect found. Per neat-boar-16's ruling, this lands before bold-carp-423 rebases #12722 and the root fix onto it.
Defect
#12656's
pool_acquireheads memo held each pool file's wholeParseWithTableResultfor the life of the process. That is the occurrence transport (an index entry per occurrence), the full intern index and the allocator, for about 7k files. Its consumers read four fields of it.Bisect, 8 entries, bound
memory.maxleaf: peak RSS went from 8.21 GB at #12655 to 9.52 GB at #12656 (+1.31 GB). At 20 entries, #12656 was OOM-killed under the 16 GiB leaf that the earlier points completed in.Change
pool_acquire::HeadsReading={ module, error, local_strings, local_next }. That is exactly what the two consumers read:module_path_index::parse_module_bindingreads the module name, its span and the refusal;census_heads::project_heads_readingreads the node, the refusal, and the local strings and allocator end it relabels.ParsedFileReferences) stays per index inindex.parsed_references. Its value depends on the pool's module-name set, so a process-wide (spelling, bytes) slot would key it incompletely. That is agreed with bold-carp-423 and jolly-boar-500.Controls
One heads parse per (spelling, bytes), asserted:
entry_resolve::heads_parse_count::each_pool_file_is_heads_parsed_once_on_the_live_pool, backed by a test-only counter inheads_reading_for. Executed: 7,049 keys, max 1 parse per key.Identity differential, live pool:
entry_resolve::heads_projection_live_differential. The whole projected module node, the refusal, and the intern table and allocator each file leaves are equal to the threaded parse. Executed: 7,049 files, 0 divergent. The fixture differential and its red also pass.Peak RSS, same probe (one process, 8
dag/test/claimentries over one shared index, 16 GiBmemory.maxleaf), same base (today's main):That recovers 0.57 GB, about 43% of One heads reading per file: pool census projects it instead of re-parsing (resolver cost, step 2) #12656's +1.31 GB.
What remains of the regression: the retained file-local module node, a second heads tree beside the census's projected one. It is kept deliberately, because every index that projects the file demands it (per the ruling: retention spans the obligated lifetime). Dropping it would mean re-parsing for a second index, which is ruled out.
Known limits of the measurement: the leaf's
oom_kill 1appears in the no-fix baseline as well, and the probe process exited 0 in both runs, so it is a sibling process in the leaf (a build child), not the probe. Clippy-D warningsis clean.Hand-Rust receipt (v1 seed)
gunbc.v1_maintenance_standingv1_seed_standing). It fixes a memory regression on the floor's resolve path.cli_run::pool_acquire.Do not merge; landing goes through the operator.
🤖 Generated with Claude Code