Core Concepts

Performance report

Baseline performance numbers for parse, bind, update, and memory under realistic binding scales.

Machine and runtime

Machine: Apple M4, 16.0 GB RAM, darwin/arm64, OS 24.6.0, Node v25.4.0.

Parse scenarios run 5,000 operations per sample and do not bind expressions to models.

Benchmark script: rs-x-expression-parser/scripts/benchmark-core-concepts-performance.mjs

What this report means

Parse measures how long rs-x takes to read an expression string and build its internal node tree. Parse cost grows sub-linearly with expression size — a 63-node expression takes about 8× longer than a 1-node expression, not 63×.

Parse cache measures expression creation cost in a real app. The first time a string like a + b is used, rs-x parses it and caches a template tree. Every subsequent binding using that same string skips parsing and only clones the cached tree — 3–8× faster depending on expression size. In a table with 1,000 rows sharing the same column expressions, only the first row parses; the rest clone.

Binding measures first-time setup: rs-x attaches an expression to a specific model object and computes its initial value. This happens once per bound expression, not on every update. Bind unique binds each expression to a different model object. Bind same expression reuses the same parsed expression tree cloned across all bindings.

Single update changes one field on one model and measures how long rs-x takes to propagate that change. Only the expressions that actually read the changed field are recalculated — all other bindings in the graph are untouched. This is the common case when a user edits one cell in a table.

Bulk update changes a field on every model at once and measures total recalculation time across all bindings. This is the worst-case scenario: every bound expression must be recalculated in one pass. In practice this path is taken on full data reloads.

Memory shows median heap and peak RSS recorded while each scenario runs. Heap is the JavaScript-managed memory; RSS includes all process memory such as the V8 runtime and native buffers. In all benchmark scenarios the expression is a + b. More complex expressions with more nodes or async values will use proportionally more memory.

Conclusion

Single updates are effectively free. Regardless of how many bindings are active — 1,000 or 10,000 — a single field change propagates in ~0.09 ms. rs-x only recalculates the expressions that depend on the changed field; the rest of the graph is not touched.

Parse cache makes repeated expression strings nearly free. Clone-only creation costs 0.64–27 µs versus 5.5–81 µs for a full parse, a 3–8× saving. In a table where every row uses the same column expression, only the first row pays the parse cost.

Binding setup is a one-time cost, not a recurring one. At 1,000 bindings setup takes roughly 35 ms — imperceptible during a page load. At 10,000 bindings it is about 0.5–0.6 s, still paid once, not on every render.

Bulk update scales linearly and is predictable. At 10,000 bindings a full recalculation of every expression takes ~146 ms, which is roughly 14.6 µs per expression. This path only runs on a full data reload; incremental changes remain fast.

Memory is the practical limit at large scales. At 1,000 bindings heap usage is around 125–230 MB; at 10,000 it reaches roughly 2.4–3.3 GB. Most real UIs stay well below 5,000 simultaneously active bindings. For large tables, mount only visible rows and dispose bindings when they leave the viewport — this keeps memory flat regardless of dataset size.

Table model example. With x rows and y column expressions, active bindings are x × y and parse operations are roughly y (each column expression is parsed once, then cloned). A table of 1,000 rows × 10 columns produces 10,000 active bindings: setup ~0.5 s once, single-cell updates ~0.09 ms, full bulk reload ~146 ms.

Parse performance

Expression shape
1v027.4105.48182,414
3v0 + v134.9646.99143,003
7v0 + v1 + v2 + v352.61810.5295,025
15v0 + ... + v788.54817.7156,466
31v0 + ... + v15125.86725.1739,724
63v0 + ... + v31221.47544.2922,576

Parse cache behavior (parse+clone vs clone-only)

127.2215.443.1810.64
355.06311.019.4651.89
778.19215.6418.6803.74
15116.37923.2835.8707.17
31205.56541.1176.12215.22
63404.93280.99133.89426.78

Binding performance (initial full evaluation)

1,00035.09225.444
3,000121.675123.711
5,000235.588228.468
10,000521.444638.054

Update performance (recalculate affected bound expressions)

1,0000.0897.904
3,0000.07729.483
5,0000.07155.091
10,0000.107146.234

What is recalculated here: bound expressions that read changed fields. In this benchmark, that means recalculating a + b for the row(s) where a changed.

Memory usage

Scenario
Parse (1 nodes)10.9100.2
Parse (7 nodes)12.298.9
Parse (15 nodes)12.595.5
Parse (3 nodes)15.9104.2
Parse (31 nodes)21.3113.4
Parse (63 nodes)23.8130.4