11# Runtime performance
22
3- Run the local runtime benchmark suite against the compiled package and the
4- pinned XState comparison versions:
3+ Run the local runtime benchmark suite against the compiled package:
54
65``` sh
76pnpm perf:runtime
@@ -15,64 +14,33 @@ The command reports:
1514- repeated child lookup and delivery to one running child;
1615- machine start-and-stop throughput;
1716- parent-with-child start-and-stop throughput;
18- - Effect-only lifecycle reference points for a suspended fiber, a queue worker,
19- a minimal actor shell, and a two-shell family;
20- - Effect-only coordination reference points for an owner-only mutable snapshot,
21- a synchronized snapshot, and a terminal ` Deferred ` latch;
17+ - generic and compiled raw-process lifecycle throughput;
2218- heap and resident-memory growth at 100, 500, and 1,000 live units, including
2319 a raw generic process, a raw compiled process, an idle statechart, two
2420 independent statecharts, a parent with one child, that relationship with
2521 child-registry observation active, and an invoked child whose active snapshots
26- are observed;
27- - lower-bound memory profiles for Effect itself: a suspended fiber, a queue
28- with a waiting fiber, a minimal mailbox/state/completion actor shell, and a
29- minimal two-shell family.
30-
31- The comparison dependencies use package aliases, so XState 5 and 6 can be
32- loaded by the same process:
33-
34- - ` xstate-v5 ` : ` xstate@5.32.5 ` , the stable v5 baseline;
35- - ` xstate-v6 ` : ` xstate@6.0.0-alpha.31 ` , the latest published v6 alpha available
36- when the harness was added.
37-
38- All implementations use the same flat counter topology, immutable events, and
39- terminal fence. The XState adapter uses ` assign ` in v5 and the v6 transition
40- function API because ` assign ` is not exported by that alpha.
22+ are observed.
4123
4224The burst benchmark reports useful counter increments per second. It enqueues
4325one final fence event after all counter events and awaits the machine's terminal
4426output, so the measured duration also amortizes that fence and terminal
4527cleanup. This measures complete queue drainage, not only the enqueue time
4628returned by ` MachineRef.send ` .
4729
48- Results are informational. Compare runs on the same machine while it is idle,
49- using the same Node.js and dependency versions. Tinybench warms each scenario
50- before collecting samples, and the memory measurements force garbage
51- collection before every observation. Each implementation's memory curve runs
52- in a fresh child process so garbage from one library cannot distort another
53- library's baseline.
54-
55- These scenarios compare observable work, not identical internals. Effect
56- Machine plans transitions synchronously and validates schema-backed state and
57- events, while its running machine provisions Effect queues, fibers,
58- synchronization, change publication, and child/invoke lifecycle machinery.
59- XState's counter is a smaller synchronous actor. Treat the comparison as an
60- application-level cost baseline, not a claim that the libraries provide the
61- same runtime guarantees.
30+ Compare runs on the same machine while it is idle, using the same Node.js and
31+ dependency versions. Tinybench warms each scenario before collecting samples,
32+ and the memory measurements force garbage collection before every observation.
33+ Each memory profile runs in a fresh child process so garbage from one profile
34+ cannot distort another profile's baseline.
6235
6336The fitted heap slope is the primary idle-capacity metric. Compare adjacent
6437profiles to attribute retained memory: raw process to idle statechart isolates
6538statechart machinery, two independent machines to parent-with-child isolates
6639relationship bookkeeping, while the two observed parent-child profiles isolate
6740registry and invoked-snapshot observation. Invoked snapshot mapping uses a
6841direct, state-scoped delivery path; its profile measures the retained callback
69- and mapping state rather than a general ` changes ` stream subscription. The
70- Effect profiles are primitive lower bounds, not feature-equivalent competitors.
71- The Effect throughput reference points similarly bound individual runtime
72- operations rather than predicting a complete machine by themselves. In
73- particular, the owner-only mutable snapshot is safe only when one process fiber
74- owns active state writes; terminal arbitration and externally visible
75- observation still require separate coordination.
42+ and mapping state rather than a general ` changes ` stream subscription.
43+
7644Resident memory is reported as a raw diagnostic because V8 and the
7745operating-system allocator can reuse already committed pages. The
7846capacity-per-GiB value is a linear estimate that excludes shared process
@@ -105,9 +73,11 @@ benchmark workflow has read-only repository access; a separate trusted
10573` workflow_run ` workflow validates the uploaded JSON before receiving permission
10674to update the comment.
10775
108- The implementation lives in ` scripts/runtime-performance.mjs ` ; the Effect
109- Machine fixture is in ` perf/runtime/counter.mjs ` , and the comparison adapter is
110- in ` perf/runtime/xstate.mjs ` . Effect runtime reference fixtures are in
111- ` perf/runtime/effect-runtime.mjs ` . Add cross-library scenarios only when every
112- implementation performs equivalent observable work and the result is consumed
113- and checked so the JavaScript engine cannot discard it.
76+ The required pull request check rejects throughput decreases above both 15%
77+ and three times the observed process-level median absolute deviation. Heap per
78+ unit uses the same variability rule with a 20% floor. RSS remains informational
79+ because hosted-runner and allocator behavior makes it substantially noisier.
80+
81+ The implementation lives in ` scripts/runtime-performance.mjs ` , and the Effect
82+ Machine fixture is in ` perf/runtime/counter.mjs ` . Every scenario consumes and
83+ checks its result so the JavaScript engine cannot discard the measured work.
0 commit comments