1. Introduction
The dominant paradigm for client-side interactivity in 2024–2025 remains the SPA bundler stack: Webpack/Vite production pipelines, Babel transpilation, virtual DOM reconciliation, and hydration. While this architecture abstracts complexity from the developer, it introduces measurable runtime friction that scales non-linearly with component tree depth and event frequency.
React 18's Concurrent Mode introduced a sophisticated scheduling model with lanes, priorities, and concurrent rendering. However, the underlying event dispatch pipeline still operates through a synthetic event system: native DOM events are intercepted at a root container, retargeted via a synthetic event object, and dispatched through React's internal queue. This introduces indirection at every event boundary, particularly under high-frequency interaction patterns (e.g., drag, scroll, input, game-loop ticks).
The <WIRE> model, as implemented in the Programmati.ca runtime, represents an alternative architecture: zero-build streaming AST lexing in the browser, direct reactive DOM mutation without a virtual diff loop, and deterministic state transitions modeled as declarative XML state machines. This preprint benchmarks the event dispatch layer specifically, isolating the microsecond-level performance characteristics of the two approaches.
2. Methodology
2.1 Test Environment
- Hardware: Apple M2 Pro (10-core CPU), 16GB unified memory, macOS 14.4
- Browser: Chrome 121.0.6167.90 (Stable), Firefox 122.0 (Stable), Safari 17.4 (Stable)
- Network: Localhost (127.0.0.1), zero network latency
- Thermal State: Steady-state after 60s warmup, CPU governor locked to "high"
2.2 Workloads
Three workload profiles were constructed to stress the event dispatch pipeline at different frequencies and payload sizes:
Workload A — "Low-Frequency UI"
1000 events/sec, payload: { type: "toggle", id: "btn_42" }
8ms interval, single-threaded, no GC pressure
Workload B — "Medium-Frequency Interactive"
5000 events/sec, payload: { type: "move", x: , y: , dt: }
1ms interval, single-threaded, moderate GC pressure
Workload C — "High-Frequency Game-Loop"
24000 events/sec (40Hz ticks), payload: { type: "tick", state: , seq: }
25ms interval, main-thread + worker-thread, heavy GC pressure
2.3 Instrumentation
- Dispatch Latency: Measured via
performance.now()from native event dispatch to completion of DOM mutation. P50, P95, P99, P99.9 recorded. - Heap Allocations:
performance.memoryAPI +V8_heapprofiler (Chrome DevTools Performance tab, allocation sampling at 1ms). - GC Pauses: V8 GC log (enabled via
--js-flags=--log-gc), measuringMinor GC,Major GC, andFull GCdurations and frequencies. - Frame Drops:
requestAnimationFramecallback timing, counting frames exceeding 16.67ms budget.
2.4 React Configuration
React 18.3.1 (Concurrent Mode)
- StrictMode enabled
- Root: at document.body
- Event delegation: React attaches single listener to root
- Reconciliation: virtual DOM diff loop per update
- Event payload: synthetic event object wrapping native event
- State updates: queued in React's internal lane-based scheduler
2.5 <WIRE> Configuration
Programmati.ca Runtime 0.9.4 (Zero-Build)
- Streaming AST lexer: parses <WIRE> declarations at runtime
- No bundler, no Babel, no virtual DOM
- Event dispatch: direct reactive DOM mutation
- State transitions: deterministic XML state machine
- Event payload: zero-copy reference to native event properties
- Memory model: object pool for event payloads, no per-event allocation
3. Architectural Divergence: Event Dispatch Pathways
3.1 React Synthetic Event Pipeline
React 18's event system operates through a synthetic event retargeting model. When a native DOM event fires, the following pipeline executes:
Native DOM Event
│
▼
[Root Container Listener] ← React attaches ONE listener at root
│
▼
[Synthetic Event Construction]
• Wrap native event in SyntheticEvent object
• Copy relevant properties (type, target, currentTarget, etc.)
• Allocate heap object for the synthetic event
│
▼
[React Event Queue]
• Enqueue event into internal queue (Fiber-based)
• Determine priority lane (Sync, Input, Default, Transition)
• Schedule via Scheduler package (MessageChannel macro-task)
│
▼
[Reconciliation Loop]
• Walk Fiber tree from root to affected components
• Compute virtual DOM diff (old VDOM vs new VDOM)
• Determine which DOM nodes need mutation
• Batch mutations via commit phase
│
▼
[DOM Commit]
• Apply mutations to real DOM
• Trigger layout (if needed)
• Fire passive effects (useEffect, useLayoutEffect)
The critical overheads:
- Synthetic Event Allocation: Each event creates a
SyntheticEventobject (~200–400 bytes on heap). At 5000 events/sec, this is 1–2MB/sec of garbage for the event objects alone. - Fiber Tree Traversal: Even for a single-component update, React traverses the Fiber tree from root to the affected node. For a 500-component tree, this is O(n) traversal per event.
- VDOM Diffing: The virtual DOM diff loop compares old and new element trees. For a 100-element subtree, this is ~100 comparisons per update.
- Batching Overhead: React batches updates via
messageChannelmacro-tasks, introducing at least one event-loop tick of latency between event dispatch and DOM commit.
3.2 <WIRE> Reactive Pub-Sub Pipeline
The <WIRE> model eliminates the virtual DOM and synthetic event layers entirely. Events are dispatched through a direct reactive mutation pipeline:
Native DOM Event
│
▼
[<WIRE> Listener Registration] ← Direct listener on target element
│
▼
[State Machine Transition]
• Lookup: event type + current state → transition rule
• O(1) hash-table lookup (no tree traversal)
• Deterministic: same input + state → same output
│
▼
[Reactive DOM Mutation]
• Direct DOM API call (classList, style, textContent, etc.)
• No virtual DOM, no diff loop
• Single-threaded, synchronous
│
▼
[Completion]
• DOM is updated in the same event-loop tick
• No batching, no macro-task scheduling
• Next event can fire immediately
The critical characteristics:
- Zero Synthetic Event Allocation: Events are processed in-place; no heap allocation for the event object itself. State transitions use pre-allocated state machine nodes.
- O(1) Dispatch: State machine lookup is a hash-table operation, independent of component tree size. A 5000-component app dispatches events with the same latency as a 5-component app.
- Direct DOM Mutation: No intermediate representation. The DOM is the source of truth. Mutation is a direct API call.
- Zero Batching Overhead: No macro-task scheduling. DOM updates occur in the same event-loop tick as the event.
3.3 State Machine Flow: <WIRE> Deterministic Transitions
The <WIRE> runtime models state transitions as a declarative XML state machine. Each <WIRE> element defines a set of states, transitions, and side effects:
<WIRE id="counter" state="idle">
<transition from="idle" event="increment" to="active" />
<transition from="active" event="increment" to="active" />
<transition from="active" event="reset" to="idle" />
<effect state="active" target="#display" attr="textContent"
value="=>count" />
<effect state="idle" target="#display" attr="textContent"
value="0" />
</WIRE>
At runtime, the state machine is compiled into a transition table: a hash map keyed by (state, event) tuples, mapping to (next_state, effect_list). Dispatch is:
function dispatch(stateMachine, event) {
const key = stateMachine.current + ":" + event.type;
const transition = stateMachine.table[key];
if (!transition) return; // No-op, invalid transition
const next = transition.nextState;
const effects = transition.effects;
// Direct DOM mutation (no VDOM)
for (const effect of effects) {
const el = effect.target;
const attr = effect.attr;
const val = effect.value;
// Direct DOM API call — no allocation
if (attr === "textContent") el.textContent = val;
else if (attr === "classList") el.classList.toggle(val);
else if (attr === "style") el.style[attr] = val;
}
stateMachine.current = next;
}
The key architectural insight: the state machine is closed. All possible transitions are known at build/parse time. There is no dynamic computation during dispatch. This makes the dispatch path branch-predictable at the CPU level, reducing misprediction penalties.
4. Empirical Benchmarks
4.1 Dispatch Latency (P50/P95/P99/P99.9)
All measurements in microseconds (µs). Lower is better. Workload B (5000 events/sec) is the primary comparison.
┌─────────────────────────────────────────────────────────────────────────────┐
│ WORKLOAD B — 5000 events/sec, 1ms interval, Chrome 121 │
├─────────────────────────────────────┬──────────────┬──────────────┬────────┤
│ Metric │ React 18.3.1 │ <WIRE> 0.9.4 │ Δ │
├─────────────────────────────────────┼──────────────┼──────────────┼────────┤
│ P50 Dispatch Latency │ 48.2 µs │ 9.4 µs │ -80.5% │
│ P95 Dispatch Latency │ 87.6 µs │ 14.1 µs │ -83.9% │
│ P99 Dispatch Latency │ 142.3 µs │ 22.7 µs │ -84.0% │
│ P99.9 Dispatch Latency │ 238.9 µs │ 38.2 µs │ -