You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Shipped Perry carries ~3× the RSS it needs to. The 16 MB nursery cap that #7056 identified as the entire footprint win is gated behind two knobs that both default to off, so it has never been active in a shipped build.
Measured on today's collector (statepoints default, 148f97bc2), 8 gc_ratchet probes, as a 2×2 — because the one-armed version of this measurement says something false:
no scavenge
scavenge
no cap(ships today)
799,604,736 B
799,604,736 B (+0%)
cap 16 MB
537,165,824 B (−33%)
245,006,336 B (−69%)
Wall time over the same probes:
arm
RSS
wall
ships today
baseline
2017 ms
cap only
−33%
2488 ms (+23%)
cap + scavenge
−69%
2085 ms (+3%)
scavenge only
+0%
2066 ms (+2%)
Read the row and the column, not one cell. Scavenge alone buys nothing — the top row is identical to the byte. The cap alone trades a third of the footprint for a quarter of the wall time. Only together are they worth it: the cap makes collections frequent, and scavenge makes them evacuating (O(live) copying) rather than O(heap) sweeps, so frequency becomes cheap.
This is why #7056's recommendation — "decouple the 16 MB cap and keep it" — is not on its own sufficient. Acting on it literally ships the −33%/+23% arm.
Why now
Enabling scavenge also defers alloc-point collections to a precise safepoint instead of collecting behind a forced conservative scan. That was unreasonable while precise roots were opt-in; it became reasonable when #7370 made native roots the default.
What blocks it
I prototyped the change (cap unconditional + gc_scavenge_enabled() default-on) and it is not landable as-is:
11/11 gc_ratchet probes stay byte-identical to Node, and the gap suite tracked the 447/19/13 baseline cleanly for the 200+ tests measured before I stopped.
But 26 gc::tests::* fail, against a flaky baseline of 1–4 (cargo-test: perry-runtime's suite fails a different number of tests on every run #7365). They are concentrated in debt_pacer, incremental_sweep_reclaim, temp_roots, root_words, triggers — the budgeted/incremental path that scavenge's deferral bypasses. A representative failure asserts a collection count of 1 and gets 0, which is the deferral doing exactly what it is supposed to.
One cause is already understood and fixed in the prototype: force_legacy_gc_pacing() un-capped the trigger by pinning gc_moving_loop_polls_enabled() off, which stops working once the cap is unconditional. The guard now suppresses the cap directly, and that fixed the tests which use it.
But mechanically pinning legacy pacing across incremental_sweep_reclaim fixed only 3 of 10. So the remaining failures are not a pacing-mode mismatch — each needs a judgement call on whether it asserts behaviour that legitimately changed, or catches a real problem. Rushing that is how a real regression gets pinned as "expected".
Suggested shape
Land the force_legacy_gc_pacing fix on its own — it is a latent bug today: the guard silently stops working the moment anyone makes the cap unconditional.
Triage the ~26 tests individually.
Then flip both defaults together, never one alone.
Per the kill-policy, the new PERRY_GC_SCAVENGE=0 off-state needs a CI arm; there is none today (grep over .github/workflows returns nothing).
Summary
Shipped Perry carries ~3× the RSS it needs to. The 16 MB nursery cap that #7056 identified as the entire footprint win is gated behind two knobs that both default to off, so it has never been active in a shipped build.
Measured on today's collector (statepoints default,
148f97bc2), 8gc_ratchetprobes, as a 2×2 — because the one-armed version of this measurement says something false:Wall time over the same probes:
Read the row and the column, not one cell. Scavenge alone buys nothing — the top row is identical to the byte. The cap alone trades a third of the footprint for a quarter of the wall time. Only together are they worth it: the cap makes collections frequent, and scavenge makes them evacuating (O(live) copying) rather than O(heap) sweeps, so frequency becomes cheap.
This is why #7056's recommendation — "decouple the 16 MB cap and keep it" — is not on its own sufficient. Acting on it literally ships the −33%/+23% arm.
Why now
Enabling scavenge also defers alloc-point collections to a precise safepoint instead of collecting behind a forced conservative scan. That was unreasonable while precise roots were opt-in; it became reasonable when #7370 made native roots the default.
What blocks it
I prototyped the change (cap unconditional +
gc_scavenge_enabled()default-on) and it is not landable as-is:gc_ratchetprobes stay byte-identical to Node, and the gap suite tracked the 447/19/13 baseline cleanly for the 200+ tests measured before I stopped.gc::tests::*fail, against a flaky baseline of 1–4 (cargo-test: perry-runtime's suite fails a different number of tests on every run #7365). They are concentrated indebt_pacer,incremental_sweep_reclaim,temp_roots,root_words,triggers— the budgeted/incremental path that scavenge's deferral bypasses. A representative failure asserts a collection count of 1 and gets 0, which is the deferral doing exactly what it is supposed to.One cause is already understood and fixed in the prototype:
force_legacy_gc_pacing()un-capped the trigger by pinninggc_moving_loop_polls_enabled()off, which stops working once the cap is unconditional. The guard now suppresses the cap directly, and that fixed the tests which use it.But mechanically pinning legacy pacing across
incremental_sweep_reclaimfixed only 3 of 10. So the remaining failures are not a pacing-mode mismatch — each needs a judgement call on whether it asserts behaviour that legitimately changed, or catches a real problem. Rushing that is how a real regression gets pinned as "expected".Suggested shape
force_legacy_gc_pacingfix on its own — it is a latent bug today: the guard silently stops working the moment anyone makes the cap unconditional.PERRY_GC_SCAVENGE=0off-state needs a CI arm; there is none today (grepover.github/workflowsreturns nothing).Prototype is on
perf/nursery-cap-by-default.