ReviewOS

zig-utils/zig-js

Perf: retune 512-byte slab chunks after Object shrink

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

Parent: #86 Related: #62, rejected #78/#79

Evidence

The #86 cold-state sidecar and Symbol/BigInt payload union reduce Debug Object from 640 to 448 bytes. With zig-gc's 48-byte header, ordinary cells are 496 bytes and correctly enter the existing 512-byte slab. Object churn improves materially (latest higher-work paired run: 558.975 ms -> 505.292 ms, -9.60%, 4/5 wins, exact checksum).

The slab geometry still classifies only 1024/2048-byte buckets as large-chunk buckets. The new dominant 512-byte object bucket therefore uses 64 KiB chunks (128 cells) instead of the old object bucket's 384 KiB chunks. Across 14 order-balanced cold-lifecycle pairs, the current #86 candidate has a small +0.27% median paired cost and only 4/14 wins, consistent with extra chunk growth/address-index maintenance during context construction.

Proposed experiment

  • Keep the existing power-of-two classes unchanged.
  • Extend the existing large-chunk policy to the 512-byte bucket; do not add or replace a class (the failed direction in #78/#79).
  • Update focused allocator geometry tests/comments.
  • Compare against both current main and the #86 small-chunk candidate.

Acceptance

  • Exact ownership, freelist reuse, trimming, bulk teardown, heap-budget accounting, and parallel locking tests remain green.
  • Cold independent context lifecycle shows a repeatable improvement over the #86 small-chunk candidate.
  • object_churn retains or improves its material #86 win with exact checksum.
  • Shared scaling and representative steady-state rows do not regress.
  • Revert and close as rejected if the paired performance gate is neutral or negative.
9 days ago

Rejected after order-balanced A/B against the #86 small-chunk candidate; no allocator-policy code retained.

The focused GC backing suite passed 18/18 with zero leaks, and extending the 384 KiB policy to the 512-byte bucket was directionally faster but not material or stable enough to justify six times the per-chunk reservation:

  • independent cold lifecycle (arithmetic 160, five samples/process): 5/7 wins, median paired -0.34%, median-of-process-medians 60.292 -> 60.121 ms (-0.28%)
  • single object_churn 200, five samples/process: 4/7 wins, median paired -0.59%, median-of-process-medians 487.298 -> 484.345 ms (-0.61%)
  • exact checksums in all samples
  • both rows contained severe host-load outliers

The 512-byte bucket therefore keeps its 64 KiB chunks. #86 retains the existing power-of-two class and geometry; the material gain comes from the 640 -> 448 byte Object payload reduction, not chunk over-reservation.

9 days ago

Reopened with a narrower policy after the required shared-scaling audit exposed the missing case.

The #86 512-byte/small-chunk candidate regressed quick shared object_churn at every lane versus current main (jobs=5, three samples/process, three order-balanced pairs): 0/3 wins at lanes 1/2/4 and 1/3 at lane 8, with +1.4% to +25.7% paired elapsed time. Exact checksums held.

The already-built always-large probe repairs that pressure versus the #86 small-chunk form:

  • 1 lane: -8.46%, 3/3 wins
  • 2 lanes: -4.03%, 3/3 wins
  • 4 lanes: -3.94%, 2/3 wins
  • 8 lanes: -2.14%, 2/3 wins
  • exact checksum at every lane/sample

Revised implementation: use 384 KiB chunks for 512-byte cells only when GcCellBacking.parallel is enabled; retain 64 KiB 512-byte chunks for single-mutator/cold contexts. This targets synchronized chunk-growth pressure without the always-large memory reservation that caused the first rejection.

9 days ago

Implemented and pushed to main in 4bff4dd5.

The final policy keeps serial 512-byte slabs at 64 KiB, while threaded contexts select 384 KiB 512-byte slabs before private realm bootstrap. Geometry is independent from the parallel synchronization flag, so bootstrap gets the intended chunk size without paying lock overhead before the context is published.

Paired ReleaseFast shared-realm object_churn diagnostics against the pre-change main binary, three pairs per lane with exact checksum agreement:

  • 1 lane: -5.29%, 3/3 wins.
  • 2 lanes: -3.51%, 3/3 wins.
  • 4 lanes: -8.48%, 3/3 wins.
  • 8 lanes: -13.08%, 3/3 wins.

The focused backing suite passed 19/19, and the combined final validation passed 760/760 unit tests with zero leaks plus 7/7 benchmark-harness tests.

Sign in to comment on this issue.