From the validated heap census of cc: arena capacity 98.0 MB against 35.7 MB live — 62.3 MB of slack, bit-for-bit flat over a 5-minute idle soak, while node sheds its equivalent (V8 heap 93.6 → 58.8 MB, footprint 239 → 120 MB) over the same window.
Important distinction, learned the hard way: eight GC/allocator knob arms were measured and all are flat — MIMALLOC_PURGE_DELAY=0, +ARENA_PURGE_MULT=1 +PURGE_DECOMMITS=1, MAJOR_PACING_GROWTH=1 FLOOR_MB=8, SCAVENGE_NURSERY_MB=8/16, OLD_DEFRAG=1, and a positive control (PERRY_GC_MALLOC_PURGE=0) that also changed nothing. That means the existing knobs cannot reach this slack — not that the slack is unreachable. It needs an actual arena right-sizing behaviour, not tuning.
The shape: the arena grows to accommodate startup and burst peaks (cc's first launches peak at 1.7 GB footprint, later ones at ~326 MB) and then retains that capacity forever because nothing shrinks it when the live set falls. #9589 added an idle reclaim, but it fires once during startup and never re-arms — and it can only return what the GC owns.
Direction
Return capacity when live-set/capacity stays below a threshold across N collections — the standard heap-shrink heuristic — with the page-return path #9589 already built. The risk to design against is thrash: shrinking right before a burst re-grows costs more than it saves, so hysteresis and a floor matter.
Ceiling: up to ~60 MB, the largest single item in the post-regex-fix profile.
Verification
PERRY_GC_CENSUS reports arena.capacity_bytes and totals.live_bytes directly, so capacity/live ratio is the metric. A 5-minute idle soak must show capacity descending toward live (node's behaviour) rather than the current flat line; and a burst-then-idle-then-burst cycle must not show pathological re-growth cost.
From the validated heap census of cc: arena capacity 98.0 MB against 35.7 MB live — 62.3 MB of slack, bit-for-bit flat over a 5-minute idle soak, while node sheds its equivalent (V8 heap 93.6 → 58.8 MB, footprint 239 → 120 MB) over the same window.
Important distinction, learned the hard way: eight GC/allocator knob arms were measured and all are flat —
MIMALLOC_PURGE_DELAY=0,+ARENA_PURGE_MULT=1 +PURGE_DECOMMITS=1,MAJOR_PACING_GROWTH=1 FLOOR_MB=8,SCAVENGE_NURSERY_MB=8/16,OLD_DEFRAG=1, and a positive control (PERRY_GC_MALLOC_PURGE=0) that also changed nothing. That means the existing knobs cannot reach this slack — not that the slack is unreachable. It needs an actual arena right-sizing behaviour, not tuning.The shape: the arena grows to accommodate startup and burst peaks (cc's first launches peak at 1.7 GB footprint, later ones at ~326 MB) and then retains that capacity forever because nothing shrinks it when the live set falls. #9589 added an idle reclaim, but it fires once during startup and never re-arms — and it can only return what the GC owns.
Direction
Return capacity when live-set/capacity stays below a threshold across N collections — the standard heap-shrink heuristic — with the page-return path #9589 already built. The risk to design against is thrash: shrinking right before a burst re-grows costs more than it saves, so hysteresis and a floor matter.
Ceiling: up to ~60 MB, the largest single item in the post-regex-fix profile.
Verification
PERRY_GC_CENSUSreportsarena.capacity_bytesandtotals.live_bytesdirectly, so capacity/live ratio is the metric. A 5-minute idle soak must show capacity descending toward live (node's behaviour) rather than the current flat line; and a burst-then-idle-then-burst cycle must not show pathological re-growth cost.