Rust vs TypeScript Mirror Benchmarks
Run #002 · Aug 26, 2026 · Suis-MacBook-Air.local
This file compares the Rust .me kernel against the TypeScript .me kernel
with a mirror suite: same machine, same operation shapes, same measured
percentiles, and JSON output from both implementations.
Run #002 includes the Rust lazy-invalidation optimization: lazy writes now bump
source path versions and defer dependency freshness checks to read_fresh()
instead of walking every subscriber at mutation time.
Raw results:
bench-results/rust-mirror-run-002.jsonbench-results/rust-mirror-run-001.jsonbench-results/typescript-mirror-run-001.json
Versions:
| implementation | version |
|---|---|
| Rust | this-me 0.3.1 |
| TypeScript | this.me 4.0.1 |
Commands:
cd Rust
cargo run --release --bin bench-mirror > bench-results/rust-mirror-run-002.json
cd ../Typescript
node tests/Benchmarks/benchmark.mirror.ts > ../Rust/bench-results/typescript-mirror-run-001.json
What The Mirror Suite Measures
- O(k) isolation: many irrelevant nodes, one small dependent line.
- Sustained mutation: 2,000 mutations over a 4,000-node memory space.
- Push vs pull: eager and lazy recompute across real fanout.
- Secret scope: public vs secret direct write/read, plus lazy derivation.
This intentionally excludes vector search and IVF sidecars. Those are currently TypeScript-only layers, not Rust kernel parity surfaces.
1. O(k) Isolation
Lower is better. rust/ts below 1.00x means Rust was faster.
| nodes | Rust p95 ms | TypeScript p95 ms | rust/ts | Rust k | TS k |
|---|---|---|---|---|---|
| 10 | 0.0458 | 0.0334 | 1.37x | 1 | 1 |
| 100 | 0.0079 | 0.0217 | 0.36x | 1 | 1 |
| 1,000 | 0.0217 | 0.0183 | 1.18x | 1 | 1 |
| 5,000 | 0.0142 | 0.0201 | 0.71x | 1 | 1 |
| 10,000 | 0.0095 | 0.0248 | 0.39x | 1 | 1 |
Both kernels preserve the .me shape: irrelevant memory does not inflate the
recompute wave. The absolute envelope remains tiny for both implementations.
2. Sustained Mutation
| metric | Rust | TypeScript | rust/ts |
|---|---|---|---|
| p95 ms | 0.0165 | 0.0286 | 0.57x |
| p95 drift | 6.85% | -73.51% | n/a |
Both stay in a very small absolute envelope. Rust had the lower p95. TypeScript had negative drift in this run, meaning its late window was faster than its first window.
3. Push vs Pull
Eager mode pays when the source changes. Lazy mode should make mutation cheap and move recompute toward first read.
Eager Mutation P95
| fanout | Rust p95 ms | TypeScript p95 ms | rust/ts | Rust k | TS k |
|---|---|---|---|---|---|
| 10 | 0.0827 | 0.1187 | 0.70x | 10 | 10 |
| 100 | 0.8529 | 1.2004 | 0.71x | 100 | 100 |
| 500 | 7.7877 | 8.1948 | 0.95x | 500 | 500 |
| 1,000 | 10.0756 | 13.8945 | 0.73x | 1,000 | 1,000 |
| 2,500 | 28.4035 | 40.5338 | 0.70x | 2,500 | 2,500 |
| 5,000 | 55.3975 | 99.7792 | 0.56x | 5,000 | 5,000 |
Rust is ahead when both kernels do real eager fanout work.
Lazy Mutation P95
Run #001 exposed the original Rust gap: lazy mutation still walked the subscriber set and scaled with fanout. Run #002 fixes that by using source path versions and stale-on-read checks.
| fanout | Rust #001 p95 ms | Rust #002 p95 ms | TypeScript p95 ms | Rust #002 / TS | Rust k | TS k |
|---|---|---|---|---|---|---|
| 10 | 0.0086 | 0.0016 | 0.0043 | 0.38x | 1 | 1 |
| 100 | 0.0719 | 0.0017 | 0.0065 | 0.26x | 1 | 1 |
| 500 | 0.4526 | 0.0021 | 0.0056 | 0.38x | 1 | 1 |
| 1,000 | 1.0103 | 0.0021 | 0.0040 | 0.52x | 1 | 1 |
| 2,500 | 2.7658 | 0.0026 | 0.0045 | 0.58x | 1 | 1 |
| 5,000 | 5.9301 | 0.0025 | 0.0053 | 0.47x | 1 | 1 |
This is the important change in Run #002. Rust lazy mutation no longer scales
with fanout in this mirror workload. The recompute wave remains k = 1, and the
mutation path is now below the TypeScript p95 in every measured fanout.
Lazy First Read P95
| fanout | Rust p95 ms | TypeScript p95 ms | rust/ts |
|---|---|---|---|
| 10 | 0.0082 | 0.0174 | 0.47x |
| 100 | 0.0091 | 0.0238 | 0.38x |
| 500 | 0.0128 | 0.0196 | 0.65x |
| 1,000 | 0.0108 | 0.0177 | 0.61x |
| 2,500 | 0.0148 | 0.0213 | 0.69x |
| 5,000 | 0.0169 | 0.0225 | 0.75x |
The cost moved where lazy semantics say it should move: toward read_fresh().
Even there, Rust stayed below TypeScript p95 in this run.
4. Secret Scope
| case | scope | Rust p95 ms | TypeScript p95 ms | rust/ts | Rust k | TS k |
|---|---|---|---|---|---|---|
| write_read | public | 0.0021 | 0.0050 | 0.43x | 0 | 0 |
| write_read | secret | 0.0157 | 0.0501 | 0.31x | 0 | 0 |
| derivation_lazy | public | 0.0151 | 0.0250 | 0.60x | 1 | 1 |
| derivation_lazy | secret | 0.0286 | 0.5689 | 0.05x | 1 | 1 |
Rust is faster across all measured secret-scope rows in Run #002. Direct secret write/read remains slower than public, as expected, but the Rust overhead stays inside a small absolute envelope.
Interpretation
Rust .me is now more than a faithful port. It preserves the TypeScript kernel
contracts while taking advantage of Rust’s tight execution model in the hot
paths:
- O(k) isolation stays bounded.
- Sustained mutation p95 is lower.
- Eager fanout recompute is lower.
- Lazy mutation no longer walks fanout at write time.
- Direct secret write/read is lower.
The main architectural lesson from Run #002:
lazy freshness belongs to the relation between a derivation and the versions of the paths it depends on, not to a global subscriber walk during mutation.
That is closer to the .me model: a write states a fact; a fresh read resolves
whether a relation needs to be recomputed.
Status
Run #001 found the real Rust gap. Run #002 closes it for this mirror workload. The benchmark suite is now useful both as evidence and as a regression guard:
- if lazy mutation starts scaling with fanout again, the mirror output will show it immediately;
- if Rust drifts away from TypeScript semantics, the kernel contracts catch it;
- if TypeScript changes behavior, the mirror suite gives both kernels a shared comparison language.