Problem
Heap.createBatch amortizes allocation, but short checkpoint batches did not justify privately chaining every header before taking alloc_lock; the first unconditional O(1) splice regressed lower lanes. The downstream profile showed that the general fix required both a genuinely amortized tranche and a thresholded dependency path.
Implemented
For bindings that guarantee all cells use owned storage, batches of at least 64 cells now initialize and chain headers privately while marking is inactive. After taking alloc_lock, zig-gc rechecks marking and nursery state, O(1)-splices the chain, updates counters once, releases the lock, then lets the binding publish its ownership bitmap. Short batches and marking/nursery transitions retain the original compact per-cell path.
The zig-js consumer keeps a lone shared worker on 17-cell checkpoint batches and uses a bounded, explicitly rooted reserve of up to 272 objects only while multiple workers contend.
Acceptance
Problem
Heap.createBatchamortizes allocation, but short checkpoint batches did not justify privately chaining every header before takingalloc_lock; the first unconditional O(1) splice regressed lower lanes. The downstream profile showed that the general fix required both a genuinely amortized tranche and a thresholded dependency path.Implemented
For bindings that guarantee all cells use owned storage, batches of at least 64 cells now initialize and chain headers privately while marking is inactive. After taking
alloc_lock, zig-gc rechecks marking and nursery state, O(1)-splices the chain, updates counters once, releases the lock, then lets the binding publish its ownership bitmap. Short batches and marking/nursery transitions retain the original compact per-cell path.The zig-js consumer keeps a lone shared worker on 17-cell checkpoint batches and uses a bounded, explicitly rooted reserve of up to 272 objects only while multiple workers contend.
Acceptance