| title | Performance Benchmarks |
|---|---|
| description | Reproduce and interpret zig-js direct, independent-context, shared-realm, and JavaScriptCore comparison measurements. |
zig-js keeps six benchmark families separate:
zig build benchcompares the bytecode VM with the tree-walking interpreter and prints a small no-shared-state thread-scaling table.zig build benchmark-comparisondirectly compares GC-enabled zig-js and JavaScriptCore in direct single-context, independent-context steady-state, and independent-context cold-lifecycle modes. It reports zig-js shared-realm no-GIL scaling in a separate capability panel.zig build representative-benchmarkruns the versioned, dependency-free application-surface matrix fromdocs/.data/representative-benchmark-matrix-v15.json. V15 hash-inherits every V14 workload, scored mode, job count, checksum, timing boundary, engine-availability ruling, acceptance, attribution metric, shared-lane coverage, and pending-panel decision while moving peak/current resident measurements into one Mach accounting domain. Accepted historical reports are not rewritten, capability boundaries remain explicit, and quick mode is validation only.home-tool run tools/wasm-simd-benchmark.tscompares representative integer, float, shuffle, and memory Wasm SIMD kernels with scalar exports from the same module and with the system JavaScriptCore, at one and eight independent warmed contexts.zig build gc-compaction-benchmarkcompares identical fragmented heaps before and after explicit compaction, preserving retained backing, pause, fixed-point, and post-action checksum evidence.zig build gc-generation-benchmarkcompares moving and non-moving age-one and age-three nursery policies across ephemeral, mixed-survival, high-survival, and shared no-GIL workloads with exact cumulative generation telemetry.
None is an application benchmark or a universal engine score. They are small, inspectable baselines intended to reveal regressions, scaling limits, and the engine paths that deserve profiling.
V3 moves wasm_scalar from the deferred inventory into the scored matrix; V4
does the same for wasm_simd, and V5 completes wasm_memory. Both
engines evaluate the exact existing bench/wasm_simd_comparison.js bytes and
instantiate the same embedded module. Warm rows time only the selected export
export after the inherited ten-call warmup; cold rows include source evaluation,
module compilation and instantiation, invocation, context teardown, and worker
join. Each base and structural variant pair calls the same scalar or i32x4
export with the same input multiset in ascending versus descending order, so
the harness requires identical frozen checksums and equivalent tier
attribution. The SIMD export also retains the scalar export in the same module
as its checksum oracle.
The memory family keeps non-shared v128 load/store rows on direct and
independent-context modes. It deliberately does not invoke one non-shared
instance concurrently. A separate cross-engine subpanel scores the equivalent
scalar-memory export, while a zig-js capability subpanel runs disjoint atomics
over a genuinely shared module at 1/2/4/8 workers. Before that capability row
can report JavaScriptCore as N/A, every run re-probes the system JSC runner
and requires its documented JavaScriptException; no false throughput ratio is
constructed.
The separate accepted Wasm SIMD and Threads reports remain immutable historical evidence. V5 integrates their owned workload contracts into the representative matrix without copying their old timing numbers or rewriting their reports.
V6 completes promises_async_microtasks with identical source bytes covering
direct and chained Promise reactions, thenable assimilation, and a two-stage
async/await continuation. Each runner invokes the workload through its
public evaluation API, lets that API perform its normal end-of-script microtask
checkpoint, and reads the exact checksum through the same generic hook. All
three actions are inside the timed boundary. Workloads without the hook retain
their historical one-evaluation path.
The base and structural variant enqueue the same continuation graph in opposite
orders and must produce identical frozen checksums. The full job count is sized
against JavaScriptCore's faster direct row so it clears the 50 ms floor; a slow
zig-js row remains visible rather than weakening the workload. The shared-realm
panel uses per-lane state prepared before thread creation, and Thread.join()
observes each worker only after its own microtask queue has drained.
V7 scores deterministic ISO/UTC Temporal.PlainDate, Temporal.Instant, and
Temporal.Duration construction and arithmetic in zig-js direct and
shared-realm modes. Separate forward and reverse implementations change loop
direction, identifiers, property order, and operation order while retaining
identical inputs, work, and frozen checksums.
This is an availability-gated capability family, not a cross-engine ratio.
Every run evaluates the same constructor-profile probe in both public runners;
zig-js must return 1 and the system JavaScriptCore JSGlobalContext must
return 0 before JSC is reported as N/A. The scored workload never changes
conditionally, and no result is inferred from the operating-system version.
V8 completes modules_dynamic_import with two repository-owned module graphs.
Each cold sample times OS-thread and context creation, host resolution, static
load/link/evaluation, top-level await import(), microtask settlement, exact
checksum extraction, context destruction, and join. The 1/2/4/8-lane rows use
independent contexts; no module registry survives a sample or grows across it.
The structural graph changes every module/binding name, reverses source-data layout and traversal, and reverses static-import order while preserving exact work and checksums. Its dedicated attribution mode proves the two graphs select the same tiers and allocate the same number of environments.
This is also a capability boundary, not a false comparison. The installed JSC
public SDK exposes JSEvaluateScript and Objective-C evaluateScript: but no
public module evaluation/host-loader entry point. Every matrix run additionally
requires the public JSC runner to reject the exact module-syntax probe before
rendering N/A; no private SPI, source transform, or script emulation is used.
The V2 representative contract records tree-walker, bytecode-VM, baseline, optimizer, optimizer-OSR, deoptimization, bytecode-admission, generated-code, and environment-allocation observations in fresh zig-js contexts. It snapshots configuration, the inherited ten-call reduced-work warmup, and one full-work invocation separately. Every base/structural-variant pair must select the same non-empty tier set during warmup and invocation; exact counts remain visible so the equivalence gate cannot normalize an unexpected fallback away. Each pair must also allocate exactly the same number of environments in each phase, so a variant cannot hide an unnecessary heap scope behind otherwise equal tiers.
The first full-work V8 tier-only evidence is the
representative-tier-attribution-v8-2026-08-04.md
report and its
representative-tier-attribution-v8-2026-08-04.json
raw artifact. It records configuration, warmup, and invocation snapshots for
all 36 base/variant workloads at exact revision
a1b5c3932c5a49a996da59ca9c20a852938975d4; the earlier V2 artifact remains
unchanged as historical evidence.
Schema-version-2 sidecars add phase-boundary native_code and
heap objects. Native-code state distinguishes live mappings, currently
retired mappings, cumulative reclamation, and each invalidation/fallback class.
Heap state reports precise-collector live bytes, the last full-collection
baseline, and cumulative collection counts. These are engine residency gauges
and counters; they are not process RSS, allocation throughput, or GC-pause
percentiles, and the report labels them separately from timing measurements.
Schema-1 artifacts remain valid and are never rewritten to synthesize fields
that their runners did not capture.
Schema-version-3 sidecars use the V9 matrix contract and add exact VM instruction dispatches, successful VM quick-kernel entries, native-tier runtime operation entries, embedding host-callback invocations, and Wasm export dispatches. The collector rejects an incomplete execution inventory and requires every Wasm family to record invocation-phase Wasm dispatch. These are monotonic phase counters, not timings or sampled estimates. Ordinary contexts still retain the null attribution pointer; scored timing rows do not enable the counter sidecar.
Schema-version-4 sidecars use the V10 matrix contract and snapshot the owned contention profiler from before context construction. Raw fields retain every Thread lock/condition/property wait, queue/channel operation, arena/environment/object lock acquisition/contention/spin, worker run/CPU/max, and thread-join park/wait observation. Rendered phase rows summarize contention, wait, and worker deltas. A measured zero remains zero; it is not substituted for missing telemetry. Normal timing runs leave the profiler disabled.
Schema-version-5 sidecars use the V11 contract. An opt-in allocator wrapper
counts successful Context backing allocations/growth/releases and their exact
bytes from before arena and Context construction; the profiler storage itself
is deliberately outside its accounting boundary. Separate GC-cell counters
retain fresh, reused, relocation, and delegated logical cell issuance so a slab
refill is never double-counted as every cell it serves. Cell bytes use the exact
issued size-class storage, matching the storage bytes recorded when cells are
freed. Every completed minor and full collection appends its raw nanosecond pause to bounded owned storage;
sample overflow rejects the artifact. Reports derive p50/p95/max with the
nearest-rank method and use none, not zero, when a phase completes no cycle.
Ordinary contexts keep the original allocator chain and allocate no pause
sample storage.
Schema-version-6 sidecars use the V12 contract. Each fresh attribution process
reads cumulative user/system CPU and peak RSS from getrusage and the live
resident size from Mach task_info at every phase boundary. Reports subtract
the CPU counters between boundaries while preserving peak and retained RSS as
gauges. The invocation retained value is sampled after the workload host
checkpoint and before Context destruction, so it is neither allocator-requested
bytes nor the process-exit peak. These resource queries remain outside scored
timing rows.
Schema-version-7 sidecars use the V13 contract. Successful and failed baseline and optimizer attempts record total/max latency from an accepted compilation claim through publication outcome. Deoptimization latency begins only after native code returns a recoverable exit and ends when the bytecode interpreter's continuation is fully reconstructed, excluding native execution before the exit. Counts tie successful attempts to published artifacts and reconstructed exits to the deoptimization execution counter. Ordinary contexts retain a null attribution pointer and perform none of these clock reads.
Schema-version-8 sidecars use the V14 contract. In addition to the inherited
single-context and module rows, they cover both additional-panel workloads and
every supported shared-realm family/panel row at the frozen 1/2/4/8 lane
counts. Shared configuration, warmup, and invocation snapshots use the exact
scored shared harness. The invocation boundary is observed only after every
real JavaScript Thread joins, and validation requires exactly one worker run
per lane, the frozen lane-specific checksum, complete GC/allocation/process
inventories, and base/variant tier and environment-allocation equivalence at
each lane count. Families whose frozen contract says shared: false remain
excluded; the collector does not manufacture a shared result for them.
Schema-version-9 sidecars retain the V14 workload and metric contract and add
durable collection identity. After each complete three-phase execution, the
collector validates the entire ordered prefix and atomically replaces an
explicit complete: false checkpoint. A later invocation resumes only when
the matrix, quick/full mode, runner binary, host, OS, Zig, zig-js, zig-gc,
zig-regex, and JavaScriptCore identities match exactly. Date and power state
remain visible per collection segment instead of being normalized away. Only
a complete 510-snapshot inventory can transition to complete: true and
produce the Markdown report; a killed process can therefore neither publish a
partial artifact nor force already-validated workloads to run again.
Schema-version-10 sidecars use the V15 contract and retain schema 9's exact
checkpoint/resume rules. CPU remains cumulative getrusage user/system time.
Peak and current resident bytes now come from task_vm_info's
resident_size_peak and resident_size fields in the same kernel snapshot.
Darwin declares non-CPU rusage fields implementation-defined, and a real
memory-pressure run demonstrated that ru_maxrss can be lower than Mach's
simultaneous current-resident gauge. V15 preserves the valid peak/current
ordering by measuring both in one documented Mach accounting domain rather
than dropping the coherence gate.
The full-work schema-2 evidence is the
representative-tier-attribution-v8-schema-v2-2026-08-04.md
report and its
representative-tier-attribution-v8-schema-v2-2026-08-04.json
raw artifact. Its 108 snapshots name exact revision
2c7c4f31025ffb793dc474ffe6847f1705f1dc76 and retain every native-code and
heap field for all 36 workloads.
The full-work schema-3 runtime-dispatch evidence is the
representative-tier-attribution-v9-schema-v3-2026-08-04.md
report and its
representative-tier-attribution-v9-schema-v3-2026-08-04.json
raw artifact. Its 108 snapshots name exact implementation revision
e6b7dc42ed973737f937de6279cb567e25857c2d, preserve the complete execution,
admission, native-code, and heap inventories, and record nonzero invocation
dispatches for all six Wasm base/variant rows.
The full-work schema-4 synchronization evidence is the
representative-tier-attribution-v10-schema-v4-2026-08-04.md
report and its
representative-tier-attribution-v10-schema-v4-2026-08-04.json
raw artifact. Its 108 snapshots name exact implementation revision
539cdb0b7a63431b33985104ec91e88b95ac6c1c. The single-context lane-zero
workloads record exact environment and object lock acquisitions while their
contention, wait, and worker deltas measure zero; shared-lane attribution is a
separate result and is not inferred from these runs.
The full-work schema-5 allocation/GC-pause evidence is the
representative-tier-attribution-v11-schema-v5-2026-08-04.md
report and its
representative-tier-attribution-v11-schema-v5-2026-08-04.json
raw artifact. Its 108 snapshots name exact implementation revision
4ceaa61eb5298ffd9c4824c100ad3b5dc01d7634, retain 16 allocation fields and
the raw histories of 1,953 completed collections with zero overflow, and match
every minor/full sample inventory to the corresponding heap collection counters.
The full-work schema-6 process-resource evidence is the
representative-tier-attribution-v12-schema-v6-2026-08-04.md
report and its
representative-tier-attribution-v12-schema-v6-2026-08-04.json
raw artifact. Its 108 snapshots name exact implementation revision
00b5e00f2064687b7fdf9f1f7ca34445922d8340 and retain all four process-resource
fields. In the regexp invocation rows, the structural variant records
23,789,570,528 retained Context backing bytes but 2,986,147,840 live resident
bytes and a 3,172,679,680-byte fresh-process peak; the base records 160,907,264
live resident bytes. These are separate measured domains, not conversions of
allocator counters into RSS.
The full-work schema-7 tier-transition evidence is the
representative-tier-attribution-v13-schema-v7-2026-08-04.md
report and its
representative-tier-attribution-v13-schema-v7-2026-08-04.json
raw artifact. Its 108 snapshots name exact implementation revision
f0546ee8a39898d07de7209aae08d3495ac4915f and retain 17 timing fields. Across
the 36 fresh processes, the final snapshots contain 272 baseline/optimizer
attempts: 54 successful publications and 218 rejected attempts. Successful
tier-up work totals 6,678,959 ns. The same snapshots record 9,841,288 fully
reconstructed deoptimizations totaling 209,630,978 ns; publication and
deoptimization counts match their independent owner/execution counters.
Attribution is deliberately outside the timing rows. Normal contexts retain a null telemetry pointer, while the sidecar opts in to atomic counters. A full published representative report therefore preserves both its ordinary timing TSV and its attribution JSON:
zig build representative-tier-attribution -Drepresentative-tier-attribution-quick=true
zig build representative-benchmark \
-Drepresentative-benchmark-raw-out=docs/.data/representative-benchmark-YYYY-MM-DD.tsv \
-Drepresentative-benchmark-tier-attribution-out=docs/.data/representative-tier-attribution-YYYY-MM-DD.json \
-Drepresentative-benchmark-markdown-out=docs/.data/representative-benchmark-YYYY-MM-DD.mdWriting raw or Markdown representative evidence without the attribution sidecar is rejected. As with every benchmark here, the quick command validates the harness and frozen checksums but is not publication evidence.
tools/instrumentation-overhead.ts,
exposed as zig build instrumentation-overhead, alternates fresh-process
disabled/enabled pairs from one ReleaseFast runner and rejects any workload,
job-count, sample-index, or frozen-checksum mismatch. The default
execution_attribution profile compares single with single_profiled; the
native_observability profile compares single with single_observed and also
rejects generated-tier or live-code drift. The raw artifact retains invocation
wall time, complete
fresh-process wall time, process user/system CPU, peak RSS, retired instructions,
cycles, cumulative process energy, package/interrupt wakeups, page-ins, VM
page-cache hits, and voluntary/involuntary context switches for every sample.
It derives cycles, instructions, and process-energy per frozen logical job,
instructions per cycle, and jobs per joule without replacing the raw values.
Public NSProcessInfo.thermalState snapshots before and after each boundary
retain nominal/fair/serious/critical state and detect within- or across-sample
drift. No sample is discarded or reordered.
Schema v5 (with historical schema-v1/v2/v3/v4 artifacts left unchanged) keeps
each OS counter as a status-bearing observation: measured,
unavailable, or permission_denied. Before the alternating workload pairs it
runs the same number of runner-owned no-op boundary probes, then compares their
raw counter dispersion with the disabled known-work samples. Five-percent
relative standard deviation separates stable from noisy; zero-mean or
insufficient observations are indeterminate, never silently stable.
On macOS, the runner snapshots its own public
proc_pid_rusage(RUSAGE_INFO_V6) record immediately before and after the timed
JavaScript invocation. That owned boundary supplies instructions, cycles,
process energy in nanojoules, wakeups, page-ins, and VM page-cache hits;
/usr/bin/time -l independently supplies fresh-process CPU, peak RSS, and
context switches. System thermal snapshots occur immediately outside both the
wall-time and process-counter boundaries, so querying Foundation is not charged
to the JavaScript work. The artifact records that the instruction/cycle
interface exposes no multiplexing or scaling metadata. CPU cache/TLB misses,
branches, migrations, scheduler wait, frequency, package energy, and peak power
remain explicit unavailable capabilities rather than zero-valued measurements.
VM page-cache hits are never mislabeled as CPU cache behavior.
zig build instrumentation-overhead \
-Dinstrumentation-overhead-raw-out=docs/.data/instrumentation-overhead-YYYY-MM-DD.json \
-Dinstrumentation-overhead-markdown-out=docs/.data/instrumentation-overhead-YYYY-MM-DD.mdThe two states execute the exact same binary, so the fixture records that
binary's hash and size but does not pretend the runtime toggle measures
compile-time support size. In the default profile, retained RSS is unavailable
after the fresh process exits. In the native-observability profile, runner-owned
Mach task_vm_info snapshots record current RSS before and after Context
teardown in the same accounting domain as their corresponding peak values. The
runner also emits exact live artifact/code/tier counts and GDB JIT registration,
symbol-object, unwind, and lifetime counters. Validation requires equal native
code in both states, zero publisher state when disabled, live publisher storage
when enabled, and zero live debugger storage after teardown. Single-thread lock
contention remains explicitly not applicable. Quick mode uses two reduced-work
pairs to validate the harness and is never publication evidence.
zig build instrumentation-overhead \
-Dinstrumentation-overhead-profile=native_observability \
-Dinstrumentation-overhead-raw-out=docs/.data/instrumentation-overhead-native-YYYY-MM-DD.json \
-Dinstrumentation-overhead-markdown-out=docs/.data/instrumentation-overhead-native-YYYY-MM-DD.mdCounter collection is a separate --darwin-rusage runner mode. Ordinary
benchmark and production execution does not call proc_pid_rusage; the opt-in
snapshots and counter row are absent from that path. Native publisher telemetry
is likewise an explicit runner option. The execution-attribution and
native-observability profiles remain separate exact-binary A/B experiments, so
neither profile attributes the other profile's measurement work to its enabled
state.
Runs default to diagnostic. A negligible-overhead publication claim requires
-Dinstrumentation-overhead-host-class=quiet_reference; that classification
fails closed unless the captured power state is AC and known-work instructions,
cycles, and process energy are all measured and stable at the declared 5% RSD
threshold. Known-work thermal state must also remain nominal before and after
every sample with no across-sample state change. Battery, hosted,
unavailable-counter, noisy-counter, non-nominal, or thermally drifting results
stay visible as diagnostic artifacts and cannot satisfy the reference-host
gate.
The first full-work diagnostic is the seven-pair
instrumentation-overhead-diagnostic-2026-08-04.md
report with
instrumentation-overhead-diagnostic-2026-08-04.json
raw samples. It names exact revision
2fa97532c28dda7391b9ce1d083852495d10c92f and its battery/discharging power
state; it validates the complete collection path but makes no negligible-cost
claim.
The frozen independent-suite inventory keeps external benchmark candidates outside this repository and outside ordinary build/runtime dependencies. It records exact repository/tree/file pins, per-row license and applicability decisions, host-adapter limits, and the run metadata required to pin zig-js, system JSC, V8, SpiderMonkey, or QuickJS.
The first applicable candidate is a six-result diagnostic subset of the 17 Octane 2 results. Octane is explicitly retired, so it can corroborate narrow peak-throughput behavior but cannot support a modern-web or representative-suite claim. Every unselected result remains visible with its license, host, or output validation reason. JetStream 3 alpha is inventoried but excluded until its exact subtests, mixed licenses, npm preparation, compressed assets, and shell boundary are audited. Candidate status is not execution evidence.
zig build independent-suite-audit
git clone --filter=blob:none --no-checkout \
https://github.com/chromium/octane.git /absolute/outside/zig-js/octane
git -C /absolute/outside/zig-js/octane checkout --detach \
570ad1ccfe86e3eecba0636c8f932ac08edec517
zig build independent-suite-audit \
-Dindependent-suite-id=octane-2-retired \
-Dindependent-suite-checkout=/absolute/outside/zig-js/octaneOnly the explicit git clone acquisition uses the network. Checkout and
verification are offline; the verifier rejects a checkout inside zig-js, the
wrong origin/commit/tree, a dirty tree, a missing file, or any pinned-file
SHA-256 mismatch. It never acquires the suite itself.
The owned zig-js adapter runs one selected row in a fresh, separate process. It
adds only load and print to the normal engine globals, installs both through
the supported embedding environment/global-object boundary, and evaluates the
verified upstream base.js and workload bytes without transformation. The
adapter callback protocol records exact upstream result, error, score, and
auxiliary-print strings; an unsupported or failing workload remains a failed
row rather than disappearing from the candidate set.
zig build independent-suite-zig-js-bin
zig build independent-suite-zig-js \
-Dindependent-suite-checkout=/absolute/outside/zig-js/octane \
-Dindependent-suite-row=richards \
-Dindependent-suite-mode=score \
-Dindependent-suite-zig-js-revision=$(git rev-parse HEAD)The run step first performs the full checkout audit, then the runner repeats
the selected file checksums. It also rejects a dirty zig-js worktree, a source
revision other than the exact current HEAD, and an environment other than
TZ=UTC, LC_ALL=C, and LANG=C. Because zig-js links its sibling path
dependencies, the runner also rejects dirty zig-regex or zig-gc worktrees
or a noncanonical origin and retains each dependency's resolved path, exact
revision, canonical repository, and clean status. Each schema-1 JSON line
retains those complete source inputs, the runner path and SHA-256,
argv/environment, applicable row and licenses, exact loaded
sources, the evaluation-step budget and termination owner, pass/failure/skip
fields, all upstream and auxiliary outputs, the raw outer wall/CPU/peak-RSS
sample, explicit single-sample dispersion status, output validation, and both
timing boundaries. The adapter uniformly sets its harness-owned step budget to
unsigned-64 maximum for every row and mode because these pinned workloads are
finite; it does not change source, input, iteration count, or timing logic. The
collector's retained per-child process timeout owns termination, while ordinary
embedding contexts keep the engine's default runaway guard. score mode leaves
execution instrumentation off and marks tier, allocator, and pause fields as
not measured rather than emitting fake zeros. attribution mode enables the
complete named tier/admission counters, compilation/deoptimization timing, and
GC/allocation/pause summaries; its wall time and upstream score are diagnostic,
not scored performance.
On macOS, the corresponding system-JavaScriptCore adapter is a different
executable linked only to the platform framework. Both engine runners import
one frozen Octane path/checksum/license/result table, but each owns its context,
load/print callbacks, source evaluation, output capture, and JSON report.
zig-js's JSC-shaped public exports are never linked into the framework runner.
zig build independent-suite-jsc-bin
zig build independent-suite-jsc \
-Dindependent-suite-checkout=/absolute/outside/zig-js/octane \
-Dindependent-suite-row=richards \
-Dindependent-suite-jsc-adapter-revision=$(git rev-parse HEAD)The system-JSC schema-1 child records the adapter executable path/hash, exact
adapter source revision, framework bundle version, macOS build, argv and
environment. Current macOS releases provide JavaScriptCore code through the
dyld shared cache rather than a standalone framework binary, so that boundary
is explicit instead of inventing a binary hash. The public C API provides no
exact per-context tier/compilation/deoptimization or GC/allocation/pause
counters; those fields are unavailable_public_api, never zero. Framework
samples are score-only and remain separate from zig-js attribution samples.
An optional independently installed Node/V8 control is also explicit about its
programming model: it is a fresh node:vm context inside a Node process, not a
standalone d8 shell and not a browser. The adapter verifies the same pinned
sources without transformation and records the Node executable path/hash, Node
and V8 versions, adapter path/hash/revision, argv/environment, raw process
sample, outputs, validation, and timing boundaries. Node exposes no exact
per-context V8 tier or GC counters through node:vm, so those fields are
unavailable_public_api.
zig build independent-suite-node-v8-self-test
zig build independent-suite-node-v8 \
-Dindependent-suite-checkout=/absolute/outside/zig-js/octane \
-Dindependent-suite-row=richards \
-Dindependent-suite-node-v8-adapter-revision=$(git rev-parse HEAD)The step is optional and resolves node only when invoked; Node/V8 never
becomes an ordinary build or runtime dependency. Standalone V8, SpiderMonkey,
and QuickJS remain separate planned adapters rather than being conflated with
this Node-hosted control.
A single adapter invocation is a compatibility diagnostic, not publishable
performance evidence. The
independent-suite-collector.ts
zig-js repeated collection path is a separate, lossless layer:
zig build independent-suite-collect \
-Dindependent-suite-checkout=/absolute/outside/zig-js/octane \
-Dindependent-suite-zig-js-revision=$(git rev-parse HEAD) \
-Dindependent-suite-collection-out=/tmp/zig-js-octane-collection.jsonThe output path is required to be absolute and outside the zig-js worktree. That keeps every child process's clean-source proof valid while the collector atomically replaces the durable artifact after each child. Score rounds rotate the five applicable rows by sample index, and every score and attribution sample is a fresh adapter process. Attribution samples remain separate from the uninstrumented score samples.
Anti-specialization evidence is a distinct attribution-only diagnostic. The
runner first verifies the exact pinned upstream bytes, then prepends a
deterministic block comment to each evaluated source so its SHA-256 and source
offsets differ without changing its AST or workload. Scored sources are never
transformed. The recognizer audit rejects Octane revisions, hashes, and
distinctive identifiers anywhere under src/, then requires every exact and
mutated pair to pass the same output contract and select identical nonzero
execution-tier and bytecode-admission sets:
zig build independent-suite-recognizer \
-Dindependent-suite-checkout=/absolute/outside/zig-js/octane \
-Dindependent-suite-zig-js-revision=$(git rev-parse HEAD) \
-Dindependent-suite-recognizer-out=/tmp/zig-js-octane-recognizer.jsonThe artifact retains both complete child reports and raw transports. It is diagnostic recognizer evidence, never a performance result.
The schema-1 collection retains each child's raw stdout, parsed JSON when available, stderr, exit status, timeout state, and contract-validation result. Failed and malformed children are never discarded. Dispersion is the median, range, arithmetic mean, and sample relative standard deviation of only contract-valid passed score children; the artifact lists every failed or invalid sample index beside those statistics. The selected six-result geometric score is unavailable unless collection is complete and all five applicable rows pass. The command therefore writes the complete failure artifact and then returns nonzero while any row fails.
Two score samples and one separate attribution sample per row are the structural
minimum. Suite-specific publication eligibility requires at least seven score
samples per row, an all-passing complete matrix, and an explicitly selected
quiet_reference host observed on AC power. Even then, the geometric value is
labeled as the selected non-browser subset, never the full official Octane or a
browser score. Cross-engine publication additionally requires the inventoried
executable and version pins for each separate engine process.
performance-attribution-schema-v1.json
is the machine-readable contract for causal performance artifacts. Its 55
metrics cover wall and CPU time, memory and allocation, interpreter/VM/native
tier selection, compilation/deoptimization/code lifetime, runtime and Wasm
dispatch, GC phases, allocator publication, synchronization, worker lifetime,
hardware efficiency, and native-symbol coverage. Each sample must encode every
metric as measured, unavailable, or not_applicable. A missing instrument
therefore cannot silently become a zero or disappear from a report.
The schema validator also provides lossless versioned migration for the historical object-churn A/B TSV layouts. It retains every original column and the input SHA-256 while mapping fields whose scope is already known:
~/Code/Home/lang/zig-out/bin/home-tool run tools/performance-attribution.ts
~/Code/Home/lang/zig-out/bin/home-tool run tools/performance-attribution.ts \
--migrate-legacy docs/.data/object-churn-independent-id-block-ab-2026-07-29.tsv \
--output /tmp/object-churn-independent-attribution-v1.json
~/Code/Home/lang/zig-out/bin/home-tool run tools/performance-attribution.ts \
--artifact /tmp/object-churn-independent-attribution-v1.jsonFor new causal experiments, tools/exact-parent-regression.ts verifies that
the named parent is exactly the candidate commit's first parent, records hashes
of both binaries and the workload source, alternates parent/candidate process
order within every pair, enforces the exact expected checksum, and preserves
process CPU and peak-RSS observations alongside the runner's timed wall value.
Single, independent steady/cold, shared-realm, and module-cold rows additionally
retain exact-boundary instructions, cycles, process energy, and before/after
thermal state. Metrics not connected yet remain explicitly unavailable. The
tool refuses a dirty tracked zig-js, zig-gc, or zig-regex worktree.
~/Code/Home/lang/zig-out/bin/home-tool run tools/exact-parent-regression.ts /path/to/parent-runner /path/to/candidate-runner \
--parent-revision HEAD^ --candidate-revision HEAD \
--source bench/representative_comparison.js \
--mode single --workload representative_json --jobs 2200 --lanes 1 \
--material-change cpu_work \
--expected-checksum 324952086 --samples 7 \
--timed-boundary "warmed persistent context; one exact invocation" \
--raw-out docs/.data/exact-parent-YYYY-MM-DD.json \
--markdown-out docs/.data/exact-parent-YYYY-MM-DD.mdThe default host class is diagnostic, which never blocks publication. Only a
deliberately declared quiet_reference run gates: candidate wall time must be
more than 110% of its exact parent while both variants have at most 5% RSD.
Independently, reference-host efficiency publication requires parent and
candidate instructions, cycles, and process energy to each remain at or below
5% RSD and every thermal boundary to remain nominal → nominal. Unavailable or
noisy efficiency data, non-nominal state, or thermal drift blocks publication
even when wall time improves. This fail-closed rule lets #460/#461 require
efficiency evidence for changes that add threads or CPU work. The
--material-change categories are cpu_work, threads, generated_code, and
cache_traffic; when the option is omitted, independent/shared modes default
to cpu_work,threads and other modes default to cpu_work. A
generated_code or cache_traffic publication additionally requires stable
generated_code_bytes or cache_misses, so those claims fail closed while the
exact-parent metric remains unavailable on the selected host. Every raw row
remains visible instead of being hidden.
Hosted CI validates schemas, migrations, checksums, thermal drift, and combined
gate behavior without executing reference-host measurements.
The July 19, 2026 report preserves all
14 raw samples from clean benchmark
commit 9f296900abc8adf33da4e18b24d6e8f628feddb4. Explicit compaction reduced
retained backing from 8.81 MiB to 0.81 MiB and chunks from 141 to 13 (90.8%)
while preserving 6,559 live slots. Its median pause was 0.99 ms, and the
checksum-validated post-action probe was unchanged at 1.00x control throughput.
The dedicated compaction harness creates identical fresh GC contexts, retains a
large discard group followed by a smaller live tail, drops the first group, and
alternates non-moving control and explicit-compaction process order. It rejects
unequal starting heaps, backing growth, live-slot or checksum drift, missing
movement, and failure to reach an immediate dense no_candidates fixed point.
The July 29, 2026 report preserves all
112 raw samples from clean benchmark
commit 7e71eecff7a43bfb3a0b020d57231a7094790fb9. Every moving row has an exact
non-moving parent with the same trigger, workload, age, sample, and checksum.
Across the forced single-mutator rows, moving age three measured 0.63–1.01x its
non-moving parents and 0.84–1.02x moving age one. The recorded moving age-three
rows copied 480.42 MiB with zero movement failures.
The shared rows run three mutators without a context GIL. Their moving age-three median was 1,174.15 ms versus 939.65 ms for the exact non-moving parent (0.80x), and the maximum recorded moving pause was 110.56 ms. The 14 shared moving samples made 26 rendezvous attempts and completed 14 moving minors after 12 bounded retries; each row stayed within the enforced two-retry ceiling. Each moving shared row also includes its one production automatic-compaction follow-on, while conservative parents record no full collection or timeout. Maximum elapsed RSD across the accepted matrix was 13.32%.
Use quick mode while changing the harness. A dated full run can preserve every sample and its rendered report:
zig build gc-compaction-benchmark -Dgc-compaction-benchmark-quick=true
zig build gc-compaction-benchmark \
-Dgc-compaction-benchmark-raw-out=docs/.data/gc-compaction-YYYY-MM-DD.tsv \
-Dgc-compaction-benchmark-markdown-out=docs/.data/gc-compaction-YYYY-MM-DD.md
zig build gc-generation-benchmark -Dgc-generation-benchmark-quick=true
zig build gc-generation-benchmark \
-Dgc-generation-benchmark-raw-out=docs/.data/gc-generation-YYYY-MM-DD.tsv \
-Dgc-generation-benchmark-markdown-out=docs/.data/gc-generation-YYYY-MM-DD.md \
-Dgc-generation-benchmark-update-readme=trueThe July 18, 2026 SIMD report
preserves all 224 raw timing samples
from clean benchmark inputs at zig-js commit
7362c1e28c74f92b4c82e380a4ebcba038de5f1c. It ran on an 11-core Apple M3
Pro using Zig 0.17.0-dev.956+2dca73595, system JavaScriptCore framework
22625.1.20.11.3, and AC power. All 32 scored-row medians exceed 50 ms.
| family | zig-js 1 thread | zig-js 8 threads | zig-js scaling | JSC 1 thread | JSC 8 threads | JSC scaling | zig-js / JSC at 8 threads |
|---|---|---|---|---|---|---|---|
| integer | 7.73 M/s | 28.35 M/s | 3.67x | 62.02 M/s | 280.32 M/s | 4.52x | 0.10x |
| float | 7.06 M/s | 27.66 M/s | 3.92x | 63.66 M/s | 283.33 M/s | 4.45x | 0.10x |
| shuffle | 6.74 M/s | 29.00 M/s | 4.30x | 63.23 M/s | 286.75 M/s | 4.54x | 0.10x |
| memory | 8.98 M/s | 41.13 M/s | 4.58x | 53.32 M/s | 291.96 M/s | 5.48x | 0.14x |
M/s means millions of logical 128-bit state updates per second, normalized by
the exact inner-loop count. Each SIMD export has a semantically equivalent
scalar export in the same 1,166-byte module; the harness rejects disagreement
between them and between engines before scoring. At one zig-js thread, SIMD is
1.38x the scalar integer throughput, 1.27x float, 17.27x shuffle, and 1.69x
memory. Read those as instruction-path measurements: zig-js currently executes
all fixed-width SIMD through one portable architecture-independent
implementation, with no native per-architecture intrinsic path.
The one-thread timer covers only the exact warmed invocation. The eight-thread
timer covers symmetric dispatch, one invocation in each persistent worker-owned
context/module instance, and completion waits. Compilation, instantiation, and
three warm-ups are outside both timers. Independent contexts are the equivalent
public concurrency surface in both engines; zig-js shared-realm Threads are a
different capability and are not folded into the cross-engine ratios.
Reproduce the dated matrix on macOS after building the two runners:
zig build benchmark-comparison-bin -Doptimize=ReleaseFast
home-tool run tools/wasm-simd-benchmark.ts --samples 7 --lanes 8 \
--raw-out docs/.data/wasm-simd-benchmark-YYYY-MM-DD.tsv \
--markdown-out docs/.data/wasm-simd-benchmark-YYYY-MM-DD.mdThe readable module source is
bench/wasm_simd_kernels.wat; the exact bytes
embedded in bench/wasm_simd_comparison.js
were produced with pinned WABT 1.0.39 and have SHA-256
5f33169c01f36873c1ac4ec8bb07675b8d4d770a6a4f3d961454f139f1818957.
The July 29, 2026 report preserves all 1,540 raw samples. It was collected on AC power from clean zig-js commit 0f8b33c882eb53ad4d3111410d09fd71b15a10e9, zig-gc a09c01555f8b5e1485d8be5757864967942f699d, and zig-regex 2de46683b948ec895e5fa9a9e7e4c384aceccdfe using Zig 0.17.0-dev.1441+d5181a9c9 and system JavaScriptCore 22625.1.24.11.2.
| mode | lanes | wins vs JSC | zig-js / JSC throughput | zig-js scaling | JSC scaling |
|---|---|---|---|---|---|
| direct warmed context | 1 | 10 / 10 | 2.29x | — | — |
| independent steady contexts | 8 | 9 / 10 | 2.51x | 5.31x | 4.83x |
| independent cold lifecycles | 8 | 9 / 10 | 2.61x | 5.53x | 4.89x |
| shared realm, no GIL | 8 | no public-JSC equivalent | — | 4.42x | — |
The property rows favor zig-js directly by 2.79x (monomorphic) and 2.15x (four-shape polymorphic), and at eight warmed contexts by 3.06x and 2.32x. The property CPU profile attributes 46.2% of property leaves to generated code. The exact-parent packed-array profile confirms that the published arrays row still selects its guarded VM kernels. Ten reduced-size warm calls happen outside scored steady-state timers for both engines; cold lifecycle remains intentionally unwarmed. Equal checksums, alternating runner order, seven samples, and the 50 ms timing floor are enforced. Read per-row RSD in the report before interpreting small differences.
Object churn now wins directly at 130.957 ms versus JSC at 133.573 ms and scales monotonically to 4.32x warmed and 4.62x cold. It remains the one eight-lane independent loss: 242.790/255.224 ms versus JSC at 210.873/213.364 ms. The distinct shared-realm row reaches 0.74x at eight lanes; #97 tracks that final sub-1.0x lane. The historical exact-parent slab A/B and amortized-publication A/B remain causal evidence for accepted changes. Three later candidates were rejected: owned enumeration and sharded enumeration failed the eight-lane gate, while sharded pressure accounting regressed every lane. These focused runs do not replace the current complete matrix. Read the per-workload rows first; geometric means summarize one exact matrix and do not predict an application.
The July 29 stable-identity exact-parent A/B attributes that independent-context collapse to one process-global cell-ID CAS. Non-recycled per-thread ID blocks improve the eight-lane steady median by 13.71x and cold by 14.14x, restore 4.12x/4.44x throughput scaling, preserve checksums, and retain within 3.1%/1.1% of parent RSS. Its collapsed leaf profile separates the publication hotspot from allocator, rendezvous, nursery, and worker-lifecycle paths. The focused A/B does not replace the complete zig-js/JavaScriptCore matrix.
Reproduce an exact-parent pair with:
~/Code/Home/lang/zig-out/bin/home-tool run tools/independent-object-churn-profile.ts /path/to/parent-runner \
/path/to/candidate-runner \
--parent-sample /tmp/parent.sample.txt \
--candidate-sample /tmp/candidate.sample.txt \
--raw-out /tmp/object-churn-independent.tsv \
--profile-out /tmp/object-churn-independent-profile.tsv \
--markdown-out /tmp/object-churn-independent.md \
--zig-js-revision <revision> \
--parent-gc-revision <revision> \
--candidate-gc-revision <revision>The opt-in #426 phase profiler keeps those exact workload bytes and checksums while timing cooperative rendezvous, nursery prepare/trace/sweep, object-batch allocation/publication, worker lifetime, and creator join. It does not enable the per-object contention counters or alter normal benchmark output:
zig build benchmark-comparison-bin
~/Code/Home/lang/zig-out/bin/home-tool run tools/object-churn-gc-profile.ts zig-out/bin/bench-comparison-zig-js \
--raw-out /tmp/object-churn-gc.tsv \
--markdown-out /tmp/object-churn-gc.mdThe July 21 raw samples and summary attribute the eight-lane collector pause primarily to nursery sweep. The resulting #427 whole-run reclamation experiment was rejected by its exact A/B: it reduced sweep only 0.7% and regressed wall time 0.9%, so both activations were reverted. These focused profiles do not replace the complete comparison matrix or its README scores.
Both runners evaluate the exact source in bench/comparison.js. Each workload returns an exactly representable integer checksum, and the driver rejects a run if a checksum changes between samples or differs across engines at the same lane count.
| workload | one job |
|---|---|
arithmetic |
100,000 integer additions and modulo operations |
properties |
25,000 rounds mutating four properties on one object |
polymorphic_properties |
10,000 named-property read/modify/write rounds across four live receiver shapes |
object_churn |
initialize a 256-object lane-local ring, replace entries with 20,000 fresh three-property objects, read each displaced object, then checksum the bounded live tail |
arrays |
push 10,000 integers, then read and sum the array |
direct_calls |
10,000 calls through a lane-local function binding |
method_calls |
10,000 receiver-bound calls whose method reads this.bias |
closure_calls |
create and immediately call 10,000 fresh closures over a live mutable capture |
arguments_calls |
10,000 calls whose callee reads both inputs through its real arguments object |
fibonacci |
recursively evaluate fib(24) while incrementing an invocation-local observable call counter |
The compared modes are intentionally explicit:
| label | execution model |
|---|---|
| direct single | one warmed context; one exact host evaluation call per sample |
| independent steady | one warmed creator-thread-owned context and persistent OS worker per lane; identical semaphore dispatch/evaluation/completion boundary in both runners |
| independent cold | one newly spawned OS worker and newly created context per lane; thread/context/source setup through context destruction and join is timed in both runners |
| zig-js shared | one context with two unrecorded full-work no-GIL shared-realm Thread invocations; recorded JavaScript thread creation, work, and join are timed |
The first three modes are cross-engine comparisons. JSC's public API does not expose zig-js's shared-realm Thread semantics, so the shared panel has no JSC ratio: zig-js lanes can see one object graph, while the comparable JSC embedding surface is isolated contexts.
The runners are separate executables. That prevents zig-js's JavaScriptCore-shaped C exports from interposing on the real framework symbols.
The current zig-js runner gives every measured context the same process-wide,
thread-safe libc allocator. Private contexts also use it for collector
pointer-stack and weak/barrier scratch; threaded and concurrent heaps retain the
process-global page allocator required by their cross-thread scratch access. This
is the
representative embedding setup: libc
reuses freed slabs across short-lived contexts instead of forcing each arena and
GC backing allocation through page-level mmap/munmap. The allocator process
exists for the whole runner, just as JSC's internal cached allocator does. Cold
mode still times every context-owned allocation and release; only reusable
allocator infrastructure is process-scoped. The saved July 15 report uses this
same allocator policy in every zig-js mode.
For every result group:
- Build the runners in
ReleaseFast. - In direct single mode, create/configure one context and make ten reduced-size warm-up calls outside the timer, then time the exact same
__benchmarkSelected(__benchmarkJobs, __benchmarkLane)invocation bytes. Ten calls carry both runners past zig-js's current eight-entry optimizer threshold before scoring. - In independent steady mode, let every persistent OS worker create, configure, and warm its own thread-affine context. Time identical semaphore dispatch, one exact invocation per lane, and completion waits. Destroy workers and contexts after all samples.
- In independent cold mode, perform no warm-up. Time OS-thread spawn, worker-owned context creation, source/configuration evaluation, the exact invocation, context destruction, and join.
- In shared mode, configure one zig-js realm and run two unrecorded full-work shared
Threadinvocations outside the timer so one collect/reuse cycle completes before sampling, then time creation, execution, and join of JavaScriptThreads. Use the same shared path at one lane as its scaling baseline. - Alternate runner-process order deterministically for each directly compared matrix row, instead of always running one engine first.
- Run seven samples sequentially and report median, min/max, and relative standard deviation. Preserve every sample in TSV form.
- Reject a full row whose median is below 50 ms, then validate the exact expected matrix, sample indexes, within-run stability, and cross-engine checksum equality before rendering any table.
- Record power source/state and refuse to publish raw/Markdown evidence from a dirty tracked worktree.
The runner does not pin CPUs, lock frequencies, disable background work, or discard outliers. Compare medians from the same host and power state, consult the raw range, and demand repeated evidence before treating a small delta as a regression.
The JSC comparison requires macOS because it links the system JavaScriptCore.framework. On another target the build step fails with an explicit unsupported-platform message.
# Full seven-sample matrix, printed as Markdown.
zig build benchmark-comparison
# Save both the raw evidence and rendered dated report.
zig build benchmark-comparison \
-Dbenchmark-comparison-raw-out=docs/.data/benchmark-comparison-YYYY-MM-DD.tsv \
-Dbenchmark-comparison-markdown-out=docs/.data/benchmark-comparison-YYYY-MM-DD.md
# One reduced-size sample of every engine/mode/workload/lane combination.
zig build benchmark-comparison -Dbenchmark-comparison-quick=true
# Build the two machine-readable runners without executing the matrix.
zig build benchmark-comparison-bin
# Test matrix validation/publication guards without compiling or benchmarking.
zig build benchmark-comparison-test
# Regenerate the marker-delimited README scorecard from an accepted pair.
~/Code/Home/lang/zig-out/bin/home-tool run tools/benchmark-publication.ts \
--current-raw docs/.data/benchmark-comparison-YYYY-MM-DD.tsv \
--current-report docs/.data/benchmark-comparison-YYYY-MM-DD.md \
--readme README.md
# Compare two controlled, like-for-like pairs and retain every row's delta.
~/Code/Home/lang/zig-out/bin/home-tool run tools/benchmark-publication.ts \
--current-raw docs/.data/benchmark-comparison-current.tsv \
--current-report docs/.data/benchmark-comparison-current.md \
--baseline-raw docs/.data/benchmark-comparison-baseline.tsv \
--baseline-report docs/.data/benchmark-comparison-baseline.md \
--history-out docs/.data/benchmark-history-current-vs-baseline.mdUse quick mode while changing the harness. Run the full matrix once after related changes are assembled; it is measurement work, not a per-edit correctness test.
The publication tool first reruns the complete matrix, sample-index, timing-floor, checksum, and workload-count validation and then reproduces the supplied report byte for byte from its raw TSV. README replacement is marker-delimited and idempotent. Historical comparison additionally requires exact host, OS, Zig, zig-gc, zig-regex, JavaScriptCore, matrix, jobs, and sample-count matches. It normalizes volatile battery details while preserving the power source and charging state. Every engine row is retained with both medians, both RSDs, and the delta. A zig-js row gates publication only when its median worsens by more than 10% and both runs have at most 5% RSD; JSC rows remain visible controls rather than gates.
The manual-only Performance workflow
runs the same full macOS/JSC matrix with configurable sample and lane counts,
then retains the raw TSV and rendered report as one 90-day Actions artifact.
It never runs on pushes or pull requests and does not gate ordinary CI: hosted
runner timing is evidence to inspect, not an automatic comparison with the
recorded M3 Pro baseline. Every workflow artifact includes a freshly generated
README scorecard. Supplying both optional baseline paths additionally produces
a per-row history report, or rejects the run when its controlled metadata does
not match. Promote a workflow artifact into docs/.data only after that review
and rerunning any causal candidate on the reference host.
zig build bench remains the smaller internal baseline. It parses setup once, then times the same hot snippet through the bytecode VM and tree-walker. Its no-shared-state thread table answers whether aggregate compute throughput scales; the comparison suite above adds a second engine, repeat sampling, checksums, and a preserved report.
The latest saved internal run is docs/.data/bench-2026-07-04.txt. Keep it separate from the JSC report so a VM/tree-walker change cannot silently rewrite the external comparison methodology.