|
1 | 1 | # Benchmark results |
2 | 2 |
|
3 | 3 | Pure TypeScript, no native addon, no WASM. Node v22, single core. |
4 | | -Run: `pnpm --filter @metta-ts/core build && node packages/node/bench/suite.mjs` (deopt-aware mitata). |
| 4 | +Run: `pnpm bench` (builds core, then deopt-aware mitata). |
5 | 5 |
|
6 | | -## Hot paths (mitata) |
| 6 | +## Hot paths (mitata, after the Phase-15 optimization pass) |
7 | 7 |
|
8 | | -| benchmark | time/iter | notes | |
9 | | -|-----------|-----------|-------| |
10 | | -| `matchAtoms` symbol mismatch | ~10 ns | the fast-reject path | |
11 | | -| `matchAtoms` nested, binds 2 vars | ~212 ns | 21× the mismatch cost | |
12 | | -| `match` over a 1000-atom space | ~321 µs | linear scan (Phase-15 index targets this) | |
13 | | -| `fib(15)` (~1.2k recursive calls) | ~27 ms | tree-walker, no memoization | |
14 | | -| stdlib load + `(+ 1 2)` | ~13 µs | prelude atoms cached | |
15 | | -| full 270-assertion Hyperon oracle | ~62 ms | all 22 files, `buildEnv` per query | |
| 8 | +| benchmark | time/iter | |
| 9 | +|-----------|-----------| |
| 10 | +| `matchAtoms` symbol mismatch | ~9 ns | |
| 11 | +| `matchAtoms` nested, binds 2 vars | ~222 ns | |
| 12 | +| `match` over a 1000-atom space | ~201 µs | |
| 13 | +| `fib(15)` (~1.2k recursive calls) | ~17 ms | |
| 14 | +| stdlib load + `(+ 1 2)` | ~12 µs | |
| 15 | +| full 270-assertion Hyperon oracle | ~39 ms | |
16 | 16 |
|
17 | | -## Reading these |
| 17 | +## Optimization log (profile-driven, each gated by the 270/270 oracle) |
18 | 18 |
|
19 | | -Correctness-first numbers from a faithful tree-walker. The clear hot spots — per-query `buildEnv`, the linear space scan, and the `mettaEval` allocation in `fib` — are exactly what the Phase-15 roadmap targets (staging/partial-evaluation, a flat interned atom core, a bytecode VM, a `mnemonist` AtomSpace index). Every optimization is gated by the 270/270 oracle so speed never costs correctness, and must show a before/after number here. |
| 19 | +Method: `node --prof` to find hot spots, research the V8/interpreter technique, apply, re-measure, keep only if the oracle stays 270/270. Inspiration drawn from tau-prolog (a mature JS Prolog interpreter) and MORK (interned/flat representation, avoid allocation). |
| 20 | + |
| 21 | +1. **Incremental env build** — extend `MinEnv` per atom instead of rebuilding it on every query. |
| 22 | +2. **State/token short-circuit** — `subTokens`/`resolveStates`/`wrapStates` return the atom unchanged when the world has no tokens/states (skips a full tree clone on every grounded-op eval). Oracle 62 → 47 ms. |
| 23 | +3. **`applySubst` structural sharing** — skip empty substitutions and return the same reference when a subtree is unchanged (no clone). `fib` ↓ ~25%. |
| 24 | +4. **Precomputed `ground` flag** (tau-prolog's `Term.apply`: `if (this.ground) return this`) — `applySubst`/`atomVars`/`occurs` short-circuit instantly on closed terms; plus shared constant leaf type-arrays in `getTypes`. Oracle → ~39 ms; 1000-atom match 321 → 201 µs; `fib(15)` 26.6 → 17.4 ms. |
| 25 | + |
| 26 | +Net: the full oracle went from ~62 ms to ~39 ms (~37% faster) and `fib(15)` from ~26.6 ms to ~17.4 ms (~35% faster), correctness unchanged at 270/270. |
| 27 | + |
| 28 | +The deeper Phase-15 levers (staging/partial-evaluation, a MORK-style flat interned atom core, a bytecode VM, a `mnemonist` AtomSpace index) remain as the next round, same profile→research→measure→gate loop. |
0 commit comments