ReviewOS

zig-utils/zig-js

Perf: move cold Object state into sidecars to reach the 512-byte slab

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

Evidence

The current Debug DWARF layout reports value.Object at 640 bytes. zig-gc adds a 48-byte header, so every ordinary object is a 688-byte cell and lands in GcCellBacking's 1024-byte size class. Reaching the existing 512-byte class requires Object at or below 464 bytes: at least 176 payload bytes must move or overlap.

The post-#85 ReleaseFast object_churn profile confirms the structural cost:

  • platform memmove / default Object initialization: 254 sampled leaves
  • nursery sweep: 386
  • GcCellBacking.freeFn: 164
  • GcCellBacking.allocFn: 67
  • Binding.finalize: 80

Prior #78/#79 proved that merely changing or adding a non-power-of-two bucket regresses. The next viable direction is an actual payload reduction.

Parent: #62

Proposed design constraints

  • Keep hot ordinary-object fields inline: shape, slots, four inline values, prototype, dense elements/length, ownership/barrier state, backing allocator, and the property/element locks.
  • Move or overlap cold mutually exclusive exotic state: BigInt text/value, Symbol metadata, RegExp metadata, Temporal/typed-array/DataView/ArrayBuffer payloads, weak/finalization collection storage, module namespace data, and other variant-only records.
  • Prefer compact untagged payload unions where existing object-kind flags already prove exclusivity; use lazily allocated GC-owned sidecars only where states can coexist.
  • Trace and finalize every moved edge/store precisely, account sidecar bytes under heap limits, and retain no compatibility padding.
  • Add a compile-time size gate proving zig-gc cellAllocationBytes(Object) is at most 512.

Acceptance criteria

  • Object cells select the existing 512-byte bucket; no new size class.
  • Full units, TSan publication shards, test262-parallel, and benchmark harness remain green with zero leaks.
  • object_churn order-balanced A/B shows a material repeatable win with exact checksum.
  • Recheck arrays, properties, calls, polymorphic properties, context lifecycle, and shared scaling to reject any sidecar or indirection regression.
  • Land as small reviewable issue-linked commits, splitting payload representation, tracing/finalization, and benchmark evidence where practical.
9 days ago

Implemented and pushed to main in 3ab8ff14.

Result:

  • Object payload: 640 -> 448 bytes.
  • GC allocation including the zig-gc header: 688 -> 496 bytes.
  • Ordinary objects now use the existing 512-byte slab instead of the 1024-byte slab.
  • Rare mapped-arguments, weak-collection, RegExp, and FinalizationRegistry state is lazily allocated in a tracked cold sidecar.
  • Mutually exclusive Symbol and BigInt payloads share a compact inline union, so Symbols do not gain a sidecar allocation.
  • A compile-time gate rejects future Object growth beyond the 512-byte slab.

Paired ReleaseFast diagnostics against the pre-change main binary, with exact checksum agreement:

  • single-thread object_churn, 200 jobs: -6.52% median paired time, 5/5 wins.
  • cold independent arithmetic lifecycle: +0.21% median paired time, within noise, 2/5 wins.

Validation after the final design: 760/760 unit tests passed with zero leaks; 7/7 benchmark-harness tests passed. A post-rebase compile/test scan also passed after reconciling the new web-platform call sites.

Sign in to comment on this issue.