Core Concepts · Performance

Identifier-only binding performance

The most common real-world pattern: one expression reads one field. name, price, isActive. Each binding is one AST node, one watcher, one field. This page benchmarks that pattern across binding counts from 1,000 to 10,000, for both engine modes. Measured on Apple M4, Node.js v25.4.0.

What these benchmarks measure

Each binding uses a unique field name (f0, f1, …) and a unique model object. This ensures watchers are not shared across bindings — each binding creates its own watcher. This is the realistic worst case for identifier-only bindings: no sharing, maximum isolation.

Three metrics are measured:

  • Bind time — create the binding, register the watcher, do the initial read. Includes AOT plan lookup (compiled mode) or AST clone (tree mode).
  • Bulk update time — change every model field; every binding re-evaluates. Worst case: N updates, N re-evaluations.
  • Single update time — change one model field; one binding re-evaluates. Best case: O(1) regardless of total binding count.

Bind time

Bind time scales linearly with binding count. For identifier-only expressions, compiled and tree mode are within a few percent of each other — the expression is so simple that the compilation overhead in compiled mode roughly equals the AST clone overhead in tree mode. There is no clear winner here; both modes are fast.

BindingsTree (ms)Compiled (ms)Diff
1,00027.32730.129tree 9.3% faster
3,00092.21793.439tree 1.3% faster
5,000153.982156.838tree 1.8% faster
10,000318.208356.804tree 10.8% faster

Bulk update time

When every model field changes at once, every binding re-evaluates. For identifier-only expressions with unique fields this is the O(N) worst case. Even so, 10,000 bindings complete their bulk re-evaluation in under 50 ms in both modes.

At this expression complexity, compiled mode offers a modest improvement — roughly 10–15% faster at 10,000 bindings. The advantage grows significantly for more complex expressions.

BindingsTree (ms)Compiled (ms)Diff
1,0004.6055.149tree 10.6% faster
3,00013.52314.596tree 7.3% faster
5,00022.60625.580tree 11.6% faster
10,00041.53348.766tree 14.8% faster

Single update time

When one field changes, exactly one binding re-evaluates. Because rs-x uses per-field watchers, updating one field does not touch any other binding regardless of how many bindings exist. This is the key property that makes rs-x scale: update cost is O(1) with respect to total binding count.

The single update time stays below 0.1 ms across all measured binding counts — and is essentially the same at 1,000 as at 10,000 bindings. This is the most important number for applications where only a few fields change at a time (which is almost all real applications).

BindingsTree (ms)Compiled (ms)
1,0000.0720.095
3,0000.0560.063
5,0000.0670.067
10,0000.0630.073

Conclusion: single update time does not grow with binding count. This is the defining property of rs-x's reactive architecture. You only pay the O(N) cost when N fields all change simultaneously — the typical case of one or a few fields changing at a time stays O(1) no matter how large your binding graph is.

Scale reference: bind and update at larger counts

The following table extends to larger binding counts using the tree engine mode. It shows raw bind time and update performance as a reference for capacity planning.

BindingsBind (ms)Bind + init (ms)Single update (ms)Bulk update (ms)
1002.256 ms371.406 ms8.375 µs26.464 ms
50070.174 ms95.432 ms8.083 µs4.658 ms
1,00066.185 ms111.798 ms1.771 µs3.010 ms
3,000161.463 ms234.292 ms2.792 µs37.485 ms
5,000275.182 ms309.953 ms1.708 µs24.942 ms
10,000758.932 ms902.250 ms1.959 µs55.082 ms

Bind + init includes the time to complete the initial reactive evaluation cycle after binding — i.e. until every expression has its first value. This is typically higher than raw bind time because it waits for the reactive scheduler to settle.