Skip to content

perf(gc): whole-block in-place promotion — kill the ~243 ns/object promotion tax on a fully-live nursery #7742

Description

@proggeramlug

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.
  • Page-generation metadata and the remembered set when a page changes
    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 the
    remembered-set dirty scan uses to reach an object at all.
  • 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    performanceRuntime, compile-time, build-size, or memory performance

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions