ReviewOS

zig-utils/zig-js

Perf: right-size the object-only GC slab class

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

Parent: #62 Follow-up to rejected #78.

Evidence

Compile-time Heap.cellAllocationBytes values on current main:

  • object: 656 bytes (624-byte payload + 32-byte header)
  • function: 288 bytes
  • generator: 264 bytes
  • environment: 248 bytes
  • promise: 192 bytes
  • iterator helper / module namespace: 112 bytes
  • bound function: 64 bytes

The current slab classes are 64, 128, 256, 512, 1024, 2048, so ordinary objects alone cross the 512-byte boundary and consume a 1024-byte slot: 368 bytes / 35.9% internal waste per object. #78 added a 768-byte seventh class and regressed object_churn about 9.8%, consistent with adding another linear classification/ownership bucket to every hot path.

Proposed slice

  • replace the existing 1024-byte class with an exact 656-byte object class instead of adding a bucket;
  • keep the bucket count and classification depth unchanged;
  • retain the large-chunk policy for the 656-byte object class;
  • derive the regression test from Heap.cellAllocationBytes(Object) so layout drift fails visibly;
  • update allocator docs that name the large object classes;
  • preserve fallback/delegation, ownership indexes, free-list reuse, trimming, bulk teardown, heap caps, and parallel locking.

Acceptance

  • compile-time proof still shows every managed cell maps to owned slab storage;
  • focused GC backing/nursery tests pass with zero leaks;
  • GC profile confirms ordinary cells use the 656-byte class with fewer/equal chunks and correct live/free accounting;
  • seven alternating current-main/candidate process pairs preserve the exact object_churn checksum and show a meaningful median win;
  • only then run the batched full suite/harness and land a small conventional commit.
9 days ago

Rejected after two controlled six-class A/B variants against b20d30e5; no code retained.

First, focused validation exposed a build-mode layout constraint: ReleaseFast needs 656 bytes per object cell, while Debug needs 672. The replacement therefore must be at least 672 bytes to preserve the full matrix.

672-byte replacement

  • kept six buckets and the 384 KiB large-chunk policy;
  • 18/18 focused backing tests passed with zero leaks;
  • ReleaseFast profile improved from 3 chunks / 1152 slots to 2 chunks / 1170 slots;
  • alternating single object_churn 100 3: 0/7 pair wins; median 232.274250 -> 246.750041 ms (+6.23% time); all 42 checksums exact.

768-byte replacement

  • also kept six buckets, isolating it from #78's seventh-bucket search cost;
  • alternating A/B: 0/7 pair wins; median-of-process-medians 231.618125 -> 259.356583 ms (+11.98% time); all 42 checksums exact.

The existing 1024-byte power-of-two stride is materially faster despite its internal fragmentation. Future #62 work should reduce allocation/sweep operations or shrink Object enough to enter the existing 512-byte class; tightening the current slab stride is not viable. The worktree and docs are fully reverted.

Sign in to comment on this issue.