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
retain (3M records, none ever dropped) and retain_wide are the only two
benchmarks still far off Node — 0.80 s vs 0.12 and 1.33 s vs 0.15 on the pinned
M1 mini. Everything else is within 1.2×–3.6×.
The cause is that every survivor gets promoted, object by object. The GC
trace shows 2,097,155 promotions across four copying minors at 509.7 ms of
pause — 243 ns per object — spent on arena_alloc_gc_old, the memcpy, layout_transfer, old_page_account_promoted_object, the GcMoveHookKind
hooks, and the forwarding/rewrite that follows. The measured young-survival
ratio on those cycles is 1.000: not one of those objects had a reason to
move.
The structural answer is V8-style whole-page promotion: when a block is
(near-)entirely live, relabel the block as old-gen instead of evacuating it.
Nothing moves, so there is no copy, no old-gen allocation, no layout transfer,
no move hook, no forwarding pointer, and no slot anywhere in the heap or in any
address-keyed side table needs rewriting.
Things this has to get right:
The promotion decision. Block liveness is not knowable before the trace
and Eden blocks are recycled at offset 0, so per-block history means nothing.
A per-cycle decision from the previous cycle's measured survival ratio works
only if a promoting cycle still measures.
Pacing. A copying minor recycles Eden's blocks; promotion hands them away.
The arena-bytes trigger fires on arena_total_bytes(), so the allocation
runway collapses unless the capacity is given back.
Footprint. Dead objects on a promoted page are retained until the next
full. The per-cycle cost is (1 − liveness) × young bytes; the steady state
needs its own bound.
retain(3M records, none ever dropped) andretain_wideare the only twobenchmarks still far off Node — 0.80 s vs 0.12 and 1.33 s vs 0.15 on the pinned
M1 mini. Everything else is within 1.2×–3.6×.
The cause is that every survivor gets promoted, object by object. The GC
trace shows 2,097,155 promotions across four copying minors at 509.7 ms of
pause — 243 ns per object — spent on
arena_alloc_gc_old, thememcpy,layout_transfer,old_page_account_promoted_object, theGcMoveHookKindhooks, and the forwarding/rewrite that follows. The measured young-survival
ratio on those cycles is 1.000: not one of those objects had a reason to
move.
The structural answer is V8-style whole-page promotion: when a block is
(near-)entirely live, relabel the block as old-gen instead of evacuating it.
Nothing moves, so there is no copy, no old-gen allocation, no layout transfer,
no move hook, no forwarding pointer, and no slot anywhere in the heap or in any
address-keyed side table needs rewriting.
Things this has to get right:
and Eden blocks are recycled at offset 0, so per-block history means nothing.
A per-cycle decision from the previous cycle's measured survival ratio works
only if a promoting cycle still measures.
generation in place — this is where correctness lives. In particular
Old ⟹ TENURED(perf(gc): write barriers cost 16% on an all-numeric store workload — elide on provably-non-pointer stores #7511) and the old-gen page-object index, which is what theremembered-set dirty scan uses to reach an object at all.
The arena-bytes trigger fires on
arena_total_bytes(), so the allocationrunway collapses unless the capacity is given back.
full. The per-cycle cost is
(1 − liveness) × young bytes; the steady stateneeds its own bound.