ReviewOS

zig-utils/zig-gc

Perf: shard allocation metadata by registered mutator

#28
Closed chris opened this 9 days ago ·
9 days ago

Downstream: zig-utils/zig-js#97 Related: #15, #25, #26, #27

Evidence

On zig-js's shared-realm object_churn row, #26's test-and-test-and-set allocation lock reduced the exact eight-lane median from 1,319.888 ms to 1,164.047 ms (-11.8%). A post-change sample shows allocation wait is now deliberate handoff/yield; libc allocation and backing chunk growth are negligible.

The remaining serialized work under alloc_lock is global metadata publication: prepend every batch into Heap.all, initialize/index every header, update live/byte/nursery counters, and append born-concurrent cells. Increasing batch size (#25), tuning the lock (#26), and replacing the lock with one global pending-stack CAS plus an active-publisher gate (#27) have all been measured. #27 passed the full normal and TSan suite but regressed exact downstream 4/8-lane medians by 17.0%/12.6%, demonstrating that moving the convoy to one global CAS word is not sufficient.

Design direction

Add explicitly registered, stable per-mutator allocation metadata shards. A mutator should privately publish its allocated batches to its own chain/counters without touching a heap-wide write hotspot. Collection, finish/abort, and teardown may fold or walk registered shards only under an existing world-stopped/quiescent ownership boundary.

This is an ownership change, not another lock-spin experiment:

  • shard registration/unregistration must be serialized and cannot race allocation, collection, or heap teardown;
  • a thread exit must transfer or leave discoverable every published cell before its shard storage can disappear;
  • collectors must visit all shard chains exactly once and preserve the authoritative live-cell walk;
  • concurrent marking must preserve born-grey/born-concurrent behavior without tracing half-initialized payloads;
  • nursery prefix/accounting, payload classification, sweeping/unlinking, OOM recovery, and finalization order must remain exact;
  • bindings that do not opt in retain the current create/createBatch behavior.

Acceptance criteria

  • Add a generic opt-in shard API with documented lifetime and quiescence requirements; keep the existing unsharded API source-compatible.
  • Prove with multi-mutator tests that private publication does not acquire or CAS a heap-wide word per batch.
  • Preserve exact all-cell/live-byte/young-byte accounting across shard registration, thread exit, minor/full collection, abort, and heap teardown.
  • Preserve born-grey and born-concurrent safety under parallel marking; pass normal and suppression-free TSan transition tests.
  • Keep wild/stale/interior pointer classification and owned-slab header validation safe.
  • Pass the full zig-gc normal and TSan suites, then the focused zig-js semantic/GC/no-GIL gate.
  • On an order-balanced exact-parent zig-js object_churn A/B, do not regress 1/2 lanes and materially improve both 4 and 8 lanes; reject the implementation otherwise.
9 days ago

Rejected after the exact downstream gate; all prototype changes were fully reverted and neither repository has an uncommitted tracked diff.

Validation before measurement:

  • zig-gc: 37/37 normal tests, including a four-mutator transition test proving batch publication did not acquire/CAS a heap-wide word;
  • zig-js: 16/16 focused parallel-GC tests, zero leaks;
  • exact checksums at every measured lane.

Order-balanced seven-pair ReleaseFast zig-js shared object_churn A/B, 100 jobs:

  • 1 lane: parent 103.054 ms, candidate 97.920 ms (-5.0%);
  • 2 lanes: 125.061 vs 159.177 ms (+27.3% regression);
  • 4 lanes: 369.602 vs 867.557 ms (+134.7% regression);
  • 8 lanes: 997.493 vs 2,703.501 ms (+171.0% regression).

The private chains remove the heap-wide publication word but severely worsen multi-lane behavior, most likely by destroying the cross-mutator list/locality order consumed by young marking and sweep. This fails the explicit 1/2-lane non-regression and 4/8-lane material-improvement criteria by a wide margin. The result rules out deferred per-mutator all-list chains in their current form; the next #97 experiment must preserve collector traversal locality or remove the intrusive global list from the hot collector path entirely.

Sign in to comment on this issue.