Repository navigation
Description
Test I ran
json_breakdown.js times the four phases of the benchmarks/controlled/json.js workload separately with performance.now(): build 10,000 small objects, JSON.stringify the array, JSON.parse it, sum one field. 10 warm-up + 30 measured iterations, median per phase. Run three times per runtime; trials agreed within 1%.
slice run json_breakdown.js --fuel=100000000000
node json_breakdown.js
bun json_breakdown.js
deno run json_breakdown.jsjson_breakdown.js
// Phase breakdown of the benchmarks/controlled/json.js workload (n = 10,000 records).
// Each phase is timed separately with performance.now(); 10 warm-up + 30 measured iterations; median reported.
const N = 10000, SEED = 123;
function build() { const r = []; for (let i = 0; i < N; i++) r.push({ id: i, value: (i * 17 + SEED) % 100003, label: "row-" + i }); return r; }
function sum(decoded) { let s = 0; for (let i = 0; i < decoded.length; i++) s = (s + decoded[i].value) % 1000000007; return s; }
const T = { build: [], stringify: [], parse: [], sum: [] };
let check = 0;
for (let it = 0; it < 40; it++) {
const t0 = performance.now(); const recs = build();
const t1 = performance.now(); const text = JSON.stringify(recs);
const t2 = performance.now(); const dec = JSON.parse(text);
const t3 = performance.now(); check = sum(dec) + text.length;
const t4 = performance.now();
if (it >= 10) { T.build.push(t1 - t0); T.stringify.push(t2 - t1); T.parse.push(t3 - t2); T.sum.push(t4 - t3); }
}
const med = (a) => { a.sort((x, y) => x - y); return a[a.length >> 1]; };
const out = { check }; let total = 0;
for (const k of Object.keys(T)) { out[k + "_ms"] = +med(T[k]).toFixed(3); total += med(T[k]); }
out.total_ms = +total.toFixed(3);
console.log(JSON.stringify(out));Result (ms, median, lower is better)
| Phase | Slice v0.10.7 | Slice v0.10.5 | Node v24.22.2 | Bun 1.4.4 | Deno 2.9.8 | Slice vs slowest other |
|---|---|---|---|---|---|---|
| build 10,000 object literals | 6.15 | 6 | 0.13 | 0.45 | 0.29 | 13.7× slower |
JSON.stringify (275 KB out) |
0.79 | 17 | 0.98 | 0.59 | 0.61 | 1.2× faster |
JSON.parse |
7.75 | 8 | 1.73 | 1.00 | 1.46 | 4.5× slower |
| sum loop (10,000 property reads) | 1.16 | 1 | 0.035 | 0.033 | 0.036 | 32× slower |
| total | 15.8 | 32 | 2.9 | 2.0 | 2.4 | 5.4× slower |
(v0.10.5's performance.now() had 1 ms resolution.) All runtimes return the same checksum.
The independent harness run (benchmarks/controlled/run.py, warm engine, 90 calls) agrees in direction: JSON n=10,000 takes 26.8 ms on Slice v0.10.7 against 5.5 (Node), 4.0 (Deno), 3.9 (Bun); it was 55.7 ms on v0.10.5.
Reading
- The
JSON.stringifyfast path from perf,feat: v0.10.6 JSON.stringify fast-path, HTTP dispatch, source maps, fs/readline #41 works: it went from 17 ms to 0.79 ms and now beats Node. JSON.parsedid not move (8 → 7.75 ms) and is the largest single phase. Note that making it correctly rounded (see theJSON.parseprecision issue;serde_json'sfloat_roundtripis documented as roughly 2× slower for floats) will add cost here, so the two are worth looking at together.- Building objects and reading properties are 14–47× slower than the JIT runtimes. That is the interpreter-vs-JIT gap and I am recording the number, not suggesting it is a defect. For this workload it is 46% of Slice's total.
Why I am filing it
My threshold for "not acceptable" in this run was: slower than every one of Node, Bun and Deno on the same test. Three of the four phases meet it. For Slice's per-request model this is the cost of handling a ~275 KB JSON body: about 16 ms of CPU against 2–3 ms.
Environment
- Slice
v0.10.7, commitea7c70c,cargo +1.96.0 build --release --locked; Slicev0.10.5tag built the same way - Ubuntu 26.04, Linux 7.0, x86_64, i5-13500H, machine otherwise idle
Activity
siktec-lab commentedon Nov 11, 2026
Addressed in PR #65 (
v0.10.9) via the iterative ECMA-404JSON.parseengine (parse_ecma404) andPropertyMap::insert_data_property:JsStr::latin1/utf16slicing for unescaped strings andmemcpychunking into a 1-byte Latin-1 buffer for ASCII escapes (\n,\t,\",\\).fast-float2for floats.PropertyMap::insert_data_propertyslot insertion on fresh objects withoutPropertyDescriptororPropertyKeycloning overhead.