Skip to content

Commit 1b0c679

Browse files
author
MesTTo
committed
perf(bench): mitata hot-path suite + committed baseline RESULTS; pnpm bench
1 parent 4fe48bf commit 1b0c679

4 files changed

Lines changed: 95 additions & 8 deletions

File tree

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@
1313
"lint": "eslint \"packages/*/src/**/*.ts\"",
1414
"format": "prettier --write \"packages/*/src/**/*.ts\" \"*.{json,md}\"",
1515
"format:check": "prettier --check \"packages/*/src/**/*.ts\" \"*.{json,md}\"",
16-
"oracle": "vitest run packages/core/src/oracle.test.ts"
16+
"oracle": "vitest run packages/core/src/oracle.test.ts",
17+
"bench": "pnpm --filter @metta-ts/core build && node packages/node/bench/suite.mjs"
1718
},
1819
"devDependencies": {
1920
"@types/node": "^22.20.0",
2021
"@vitest/coverage-v8": "^2.1.8",
2122
"eslint": "^10.5.0",
2223
"eslint-config-prettier": "^10.1.8",
2324
"fast-check": "^3.23.1",
25+
"mitata": "^1.0.34",
2426
"prettier": "^3.8.4",
2527
"tsup": "^8.3.5",
2628
"typescript": "^5.6.3",

packages/node/bench/RESULTS.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
# Benchmark results
22

3-
Pure TypeScript, no native addon, no WASM. Node v22, single core. Run: `pnpm --filter @metta-ts/core build && node packages/node/bench/bench.mjs`.
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).
45

5-
| benchmark | time |
6-
|-----------|------|
7-
| stdlib parse + load + `(+ 1 2)` (prelude cached) | ~0.07 ms |
8-
| `fib(18)` naive double-recursion (~10.9k calls) | ~185 ms |
9-
| full 270-assertion Hyperon oracle (22 files) | ~134 ms |
6+
## Hot paths (mitata)
107

11-
These are correctness-first numbers (a faithful tree-walker; `buildEnv` is rebuilt per query, no memoization). The Phase-15 roadmap (staging/partial-evaluation, a flat interned atom core, a bytecode VM, a `mnemonist` AtomSpace index) targets the hot paths these expose, each gated by the 270/270 oracle so speed never costs correctness.
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 |
16+
17+
## Reading these
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.

packages/node/bench/suite.mjs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Deopt-aware benchmark suite (mitata) for the MeTTa TS hot paths. Pure TypeScript, no native.
2+
// Run after building core: `node packages/node/bench/suite.mjs`
3+
import { run, bench, group, summary } from "mitata";
4+
import {
5+
runProgram,
6+
matchAtoms,
7+
buildEnv,
8+
evalAtom,
9+
stdTable,
10+
sym,
11+
variable,
12+
expr,
13+
gint,
14+
parseAll,
15+
standardTokenizer,
16+
preludeAtoms,
17+
} from "../../core/dist/index.js";
18+
import { readFileSync, readdirSync } from "node:fs";
19+
import { resolve } from "node:path";
20+
21+
const tk = standardTokenizer();
22+
const CORPUS = resolve(process.cwd(), "corpus");
23+
const corpus = readdirSync(CORPUS)
24+
.filter((f) => f.endsWith(".metta") && f !== "c2_spaces_kb.metta")
25+
.map((f) => readFileSync(resolve(CORPUS, f), "utf8"));
26+
27+
// Hot-path microbenchmarks
28+
const deep = expr([variable("x"), expr([variable("y"), sym("a")]), gint(3)]);
29+
const deepT = expr([sym("p"), expr([sym("q"), sym("a")]), gint(3)]);
30+
31+
group("matcher", () => {
32+
summary(() => {
33+
bench("matchAtoms (nested, binds 2 vars)", () => matchAtoms(deep, deepT));
34+
bench("matchAtoms (symbol mismatch)", () => matchAtoms(sym("a"), sym("b")));
35+
});
36+
});
37+
38+
// A space with 1000 facts, query one
39+
const facts = Array.from({ length: 1000 }, (_, i) => expr([sym("Edge"), gint(i), gint(i + 1)]));
40+
const big = buildEnv([...preludeAtoms(), ...facts], stdTable());
41+
const query = expr([sym("match"), sym("&self"), expr([sym("Edge"), gint(500), variable("y")]), variable("y")]);
42+
group("space", () => {
43+
bench("match over 1000-atom space", () => evalAtom(big, query));
44+
});
45+
46+
// Evaluation
47+
const fibEnv = buildEnv(
48+
[
49+
...preludeAtoms(),
50+
...parseAll(
51+
"(= (fib $n) (unify $n 0 0 (unify $n 1 1 (+ (fib (- $n 1)) (fib (- $n 2))))))",
52+
tk,
53+
).map((t) => t.atom),
54+
],
55+
stdTable(),
56+
);
57+
const fib15 = parseAll("(fib 15)", tk)[0].atom;
58+
group("eval", () => {
59+
bench("fib(15) (~1.2k recursive calls)", () => evalAtom(fibEnv, fib15));
60+
bench("stdlib load + (+ 1 2)", () => runProgram("!(+ 1 2)"));
61+
});
62+
63+
group("oracle", () => {
64+
bench("full 270-assertion corpus", () => {
65+
for (const src of corpus) runProgram(src);
66+
});
67+
});
68+
69+
await run();

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)