mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-22 05:59:31 +08:00
benchmarks/scaling: 128 probes of common operations (arrays, strings, Map/Set/records, JSON, closures, exceptions, regex, dates, promises, timers, console, fs) run at 3-4 sizes under scriptc and Node with identical-stdout checks; run.mjs flags superlinear growth and >3x Node, --strace-only reports syscalls per operation, strace-workloads.mjs straces the runtime benchmark suite. Results and the full probe table: results/h40-pathology-hunt/REPORT.md. Pathologies fixed on this branch (probe ms at 1e3/1e4/1e5/1e6, Linux): i in number[] base 43/5291/timeout -> 0.03/0.20/1.5/15 (Node 0.19/0.73/6.8/39) k in Record<string> base 4.8/436/timeout -> 0.15/1.5/20/319 (Node 0.96/8.9/100/1452) re.exec /g loop base 1.1/70/7910/t.o. -> 0.49/4.1/40/416 (Node 0.47/4.6/43/398) clearTimeout n live base 1.3/10/1014/t.o. -> 1.3/3.9/38/740 (Node 3.8/9.4/46/527) new RegExp repeat base 3.9/38/368/3811 -> 0.13/1.1/11/106 (Node 0.49/2.9/23/174) console.error 2 writes/line -> 1 write/line Suite A/B, Linux x86_64 sandbox, candidate vs perf/stack/06 (--layouts=4 --runs=16), all workloads ok: | workload | baseline ms | candidate ms | change | 95% CI | verdict | size | node ms | candidate/node | | --- | ---: | ---: | ---: | --- | --- | ---: | ---: | ---: | | json-records | 202.3 | 201.9 | -0.2% | -7.0% .. +3.8% | neutral | +0.0% | 458.29 | 0.44x | | regex-logs | 166.8 | 166.4 | -0.2% | -1.6% .. +1.1% | neutral | +0.1% | 239.97 | 0.69x | | ast-interp | 268.6 | 269.9 | +0.5% | -4.6% .. +3.8% | neutral | +0.0% | 242.7 | 1.11x | | records-sort | 648.7 | 670.4 | +3.3% | -4.7% .. +10.0% | neutral | +0.0% | 222.69 | 3.01x | | template-render | 69.6 | 70.7 | +1.7% | -0.2% .. +2.6% | neutral | +0.0% | 187.3 | 0.38x | | functional-pipeline | 221.8 | 214.9 | -3.1% | -7.3% .. +0.7% | neutral | +0.0% | 249.06 | 0.86x | | async-pipeline | 197.3 | 198.2 | +0.4% | -1.2% .. +3.0% | neutral | +0.2% | 89.25 | 2.22x | | word-graph | 259.2 | 256.0 | -1.3% | -6.3% .. +3.0% | neutral | +0.0% | 469.48 | 0.55x | | alloc-trees | 318.8 | 316.8 | -0.6% | -3.0% .. +1.2% | neutral | +0.0% | 127.68 | 2.48x | | numeric-kernels | 46.4 | 46.5 | +0.1% | -1.6% .. +3.9% | neutral | +0.0% | 133.42 | 0.35x | | validate-errors | 176.5 | 178.1 | +0.9% | -3.1% .. +4.7% | neutral | +0.0% | 702.45 | 0.25x | | csv-numbers | 336.2 | 336.8 | +0.2% | -2.0% .. +1.2% | neutral | +0.0% | 500.14 | 0.67x | | log-summary | 165.7 | 164.6 | -0.6% | -5.9% .. +1.9% | neutral | +0.0% | 290.81 | 0.57x | | inventory-report | 321.9 | 320.1 | -0.6% | -6.7% .. +9.5% | neutral | +0.0% | 431.4 | 0.74x | geomean change: +0.0% geomean candidate/node: 0.768x (lower is better; goal: well below 1) macOS lane not run (all measurement in the sandbox per task instructions).
285 lines
8.2 KiB
TypeScript
285 lines
8.2 KiB
TypeScript
// Scaling probes: JSON, closures, exceptions, regex, generators, dates. Usage: misc <case> <n>
|
|
type Rec = { id: number; name: string; tags: string[]; score: number; active: boolean };
|
|
function records(n: number): Rec[] {
|
|
const out: Rec[] = [];
|
|
for (let i = 0; i < n; i++)
|
|
out.push({ id: i, name: "user" + i, tags: ["a" + (i % 5), "b"], score: i * 0.5, active: i % 3 === 0 });
|
|
return out;
|
|
}
|
|
type Node2 = { v: number; kids: Node2[] };
|
|
function tree(depth: number, fan: number): Node2 {
|
|
const kids: Node2[] = [];
|
|
if (depth > 0) for (let i = 0; i < fan; i++) kids.push(tree(depth - 1, fan));
|
|
return { v: depth, kids };
|
|
}
|
|
function jsonWide(n: number): string {
|
|
const s = JSON.stringify(records(n));
|
|
const back = JSON.parse(s) as Rec[];
|
|
return `${s.length} ${back.length} ${back[n - 1]!.name}`;
|
|
}
|
|
function jsonIndent(n: number): string {
|
|
const s = JSON.stringify(records(n), null, 2);
|
|
return `${s.length}`;
|
|
}
|
|
function jsonNested(n: number): string {
|
|
// ~n nodes in a fan-4 tree
|
|
let depth = 0;
|
|
let count = 1;
|
|
while (count * 4 < n) {
|
|
count *= 4;
|
|
depth++;
|
|
}
|
|
const t = tree(depth, 4);
|
|
const s = JSON.stringify(t);
|
|
const back = JSON.parse(s) as Node2;
|
|
return `${s.length} ${back.kids.length}`;
|
|
}
|
|
function jsonDeep(n: number): string {
|
|
// A deep chain (linked list) of n/100 levels... capped to stay under Node's recursion limit.
|
|
const depth = Math.min(n, 3000);
|
|
let s = "0";
|
|
for (let i = 0; i < depth; i++) s = "[" + s + "]";
|
|
let total = 0;
|
|
for (let k = 0; k < Math.max(1, Math.floor(n / depth)); k++) {
|
|
const v = JSON.parse(s) as unknown;
|
|
total += JSON.stringify(v).length;
|
|
}
|
|
return `${total}`;
|
|
}
|
|
function jsonManySmall(n: number): string {
|
|
let t = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
const s = JSON.stringify({ i, s: "x" + i, ok: true });
|
|
const o = JSON.parse(s) as { i: number; s: string; ok: boolean };
|
|
t += o.i + s.length;
|
|
}
|
|
return `${t}`;
|
|
}
|
|
function jsonBigString(n: number): string {
|
|
const parts: string[] = [];
|
|
for (let i = 0; i < n; i++) parts.push("line \"" + i + "\"\n\t");
|
|
const big = parts.join("");
|
|
const s = JSON.stringify({ big });
|
|
const back = JSON.parse(s) as { big: string };
|
|
return `${s.length} ${back.big.length}`;
|
|
}
|
|
function closures(n: number): string {
|
|
const fns: (() => number)[] = [];
|
|
for (let i = 0; i < n; i++) fns.push(() => i * 2);
|
|
let s = 0;
|
|
for (const f of fns) s += f();
|
|
return `${s}`;
|
|
}
|
|
function closureCounter(n: number): string {
|
|
let s = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
let c = i;
|
|
const inc = (): number => ++c;
|
|
inc();
|
|
s += inc();
|
|
}
|
|
return `${s}`;
|
|
}
|
|
function tryNoThrow(n: number): string {
|
|
let s = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
try {
|
|
s += i;
|
|
} catch (e) {
|
|
s -= 1;
|
|
}
|
|
}
|
|
return `${s}`;
|
|
}
|
|
function tryThrow(n: number): string {
|
|
let s = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
try {
|
|
if (i >= 0) throw new Error("bad " + i);
|
|
} catch (e) {
|
|
s += (e as Error).message.length;
|
|
}
|
|
}
|
|
return `${s}`;
|
|
}
|
|
function throwDeep(n: number): string {
|
|
function rec(d: number): number {
|
|
if (d === 0) throw new RangeError("deep");
|
|
return rec(d - 1) + 1;
|
|
}
|
|
let s = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
try {
|
|
s += rec(20);
|
|
} catch (e) {
|
|
s += 1;
|
|
}
|
|
}
|
|
return `${s}`;
|
|
}
|
|
function finallyLoop(n: number): string {
|
|
let s = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
try {
|
|
s += i & 3;
|
|
} finally {
|
|
s += 1;
|
|
}
|
|
}
|
|
return `${s}`;
|
|
}
|
|
function regexNew(n: number): string {
|
|
let c = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
const re = new RegExp("^item-(\\d+)$");
|
|
if (re.test("item-" + i)) c++;
|
|
}
|
|
return `${c}`;
|
|
}
|
|
function regexLiteral(n: number): string {
|
|
let c = 0;
|
|
for (let i = 0; i < n; i++) if (/^item-(\d+)$/.test("item-" + i)) c++;
|
|
return `${c}`;
|
|
}
|
|
function regexExecG(n: number): string {
|
|
const parts: string[] = [];
|
|
for (let i = 0; i < n; i++) parts.push("k" + i + "=v" + i);
|
|
const s = parts.join("&");
|
|
const re = /(\w+)=(\w+)/g;
|
|
let m: RegExpExecArray | null;
|
|
let c = 0;
|
|
while ((m = re.exec(s)) !== null) c += m[2]!.length;
|
|
return `${c}`;
|
|
}
|
|
function matchAll(n: number): string {
|
|
const parts: string[] = [];
|
|
for (let i = 0; i < n; i++) parts.push("k" + i + "=v" + i);
|
|
const s = parts.join("&");
|
|
let c = 0;
|
|
for (const m of s.matchAll(/(\w+)=(\w+)/g)) c += m[1]!.length;
|
|
return `${c}`;
|
|
}
|
|
function regexSplit(n: number): string {
|
|
const parts: string[] = [];
|
|
for (let i = 0; i < n; i++) parts.push("a" + i);
|
|
const s = parts.join(" , ");
|
|
return `${s.split(/\s*,\s*/).length}`;
|
|
}
|
|
function generator(n: number): string {
|
|
function* gen(k: number): Generator<number> {
|
|
for (let i = 0; i < k; i++) yield i;
|
|
}
|
|
let s = 0;
|
|
for (const v of gen(n)) s += v;
|
|
return `${s}`;
|
|
}
|
|
function manyGenerators(n: number): string {
|
|
function* pair(a: number): Generator<number> {
|
|
yield a;
|
|
yield a + 1;
|
|
}
|
|
let s = 0;
|
|
for (let i = 0; i < n; i++) for (const v of pair(i)) s += v;
|
|
return `${s}`;
|
|
}
|
|
function destructure(n: number): string {
|
|
let s = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
const [a, b] = [i, i + 1];
|
|
const { x, y } = { x: a, y: b };
|
|
s += x + y;
|
|
}
|
|
return `${s}`;
|
|
}
|
|
function dateNew(n: number): string {
|
|
let ok = 0;
|
|
for (let i = 0; i < n; i++) if (new Date().getTime() > 0) ok++;
|
|
return `${ok}`;
|
|
}
|
|
function dateNow(n: number): string {
|
|
let ok = 0;
|
|
for (let i = 0; i < n; i++) if (Date.now() > 0) ok++;
|
|
return `${ok}`;
|
|
}
|
|
function perfNow(n: number): string {
|
|
let ok = 0;
|
|
for (let i = 0; i < n; i++) if (performance.now() >= 0) ok++;
|
|
return `${ok}`;
|
|
}
|
|
function dateFormat(n: number): string {
|
|
let t = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
const d = new Date(1700000000000 + i * 3600000);
|
|
t += d.toISOString().length + d.getUTCHours();
|
|
}
|
|
return `${t}`;
|
|
}
|
|
function dateParse(n: number): string {
|
|
let t = 0;
|
|
for (let i = 0; i < n; i++) t += Date.parse("2024-01-" + String((i % 28) + 1).padStart(2, "0") + "T10:00:00Z") / 1e9;
|
|
return `${Math.round(t)}`;
|
|
}
|
|
function dateLocal(n: number): string {
|
|
let t = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
const d = new Date(1700000000000 + i * 3600000);
|
|
t += d.getHours() + d.getDate() + d.getTimezoneOffset();
|
|
}
|
|
return `${t}`;
|
|
}
|
|
function mathRandom(n: number): string {
|
|
let s = 0;
|
|
for (let i = 0; i < n; i++) s += Math.random();
|
|
return `${s > 0}`;
|
|
}
|
|
function mathOps(n: number): string {
|
|
let s = 0;
|
|
for (let i = 1; i < n; i++) s += Math.sqrt(i) + Math.log(i) + Math.sin(i) + Math.floor(i / 3) + (i % 7);
|
|
return `${s.toFixed(3)}`;
|
|
}
|
|
function errorStack(n: number): string {
|
|
let t = 0;
|
|
for (let i = 0; i < n; i++) t += new Error("e").message.length;
|
|
return `${t}`;
|
|
}
|
|
|
|
const which = process.argv[2] ?? "";
|
|
const n = Number(process.argv[3] ?? "1000");
|
|
const t0 = performance.now();
|
|
let out = "";
|
|
switch (which) {
|
|
case "json-wide": out = jsonWide(n); break;
|
|
case "json-indent": out = jsonIndent(n); break;
|
|
case "json-nested": out = jsonNested(n); break;
|
|
case "json-deep": out = jsonDeep(n); break;
|
|
case "json-many-small": out = jsonManySmall(n); break;
|
|
case "json-big-string": out = jsonBigString(n); break;
|
|
case "closures": out = closures(n); break;
|
|
case "closure-counter": out = closureCounter(n); break;
|
|
case "try-no-throw": out = tryNoThrow(n); break;
|
|
case "try-throw": out = tryThrow(n); break;
|
|
case "throw-deep": out = throwDeep(n); break;
|
|
case "finally": out = finallyLoop(n); break;
|
|
case "regex-new": out = regexNew(n); break;
|
|
case "regex-literal": out = regexLiteral(n); break;
|
|
case "regex-exec-g": out = regexExecG(n); break;
|
|
case "match-all": out = matchAll(n); break;
|
|
case "regex-split": out = regexSplit(n); break;
|
|
case "generator": out = generator(n); break;
|
|
case "many-generators": out = manyGenerators(n); break;
|
|
case "destructure": out = destructure(n); break;
|
|
case "date-new": out = dateNew(n); break;
|
|
case "date-now": out = dateNow(n); break;
|
|
case "perf-now": out = perfNow(n); break;
|
|
case "date-format": out = dateFormat(n); break;
|
|
case "date-parse": out = dateParse(n); break;
|
|
case "date-local": out = dateLocal(n); break;
|
|
case "math-random": out = mathRandom(n); break;
|
|
case "math-ops": out = mathOps(n); break;
|
|
case "error-new": out = errorStack(n); break;
|
|
default: out = "unknown case";
|
|
}
|
|
const t1 = performance.now();
|
|
console.log(which, n, out);
|
|
console.error("T=" + (t1 - t0).toFixed(3));
|