mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +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).
190 lines
6.0 KiB
TypeScript
190 lines
6.0 KiB
TypeScript
// Scaling probes: promises, timers, console, fs. Usage: async-io <case> <n>
|
|
import * as fs from "node:fs";
|
|
import * as os from "node:os";
|
|
import * as path from "node:path";
|
|
|
|
async function awaitChain(n: number): Promise<string> {
|
|
let s = 0;
|
|
for (let i = 0; i < n; i++) s += await Promise.resolve(i);
|
|
return `${s}`;
|
|
}
|
|
async function asyncCalls(n: number): Promise<string> {
|
|
async function step(i: number): Promise<number> {
|
|
await null;
|
|
return i & 7;
|
|
}
|
|
let s = 0;
|
|
for (let i = 0; i < n; i++) s += await step(i);
|
|
return `${s}`;
|
|
}
|
|
async function promiseAll(n: number): Promise<string> {
|
|
async function work(i: number): Promise<number> {
|
|
await Promise.resolve();
|
|
return i;
|
|
}
|
|
const ps: Promise<number>[] = [];
|
|
for (let i = 0; i < n; i++) ps.push(work(i));
|
|
const vs = await Promise.all(ps);
|
|
let s = 0;
|
|
for (const v of vs) s += v;
|
|
return `${s}`;
|
|
}
|
|
async function thenChain(n: number): Promise<string> {
|
|
let p: Promise<number> = Promise.resolve(0);
|
|
for (let i = 0; i < n; i++) p = p.then((v) => v + 1);
|
|
return `${await p}`;
|
|
}
|
|
async function timersSeq(n: number): Promise<string> {
|
|
let c = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
await new Promise<void>((r) => setTimeout(r, 0));
|
|
c++;
|
|
}
|
|
return `${c}`;
|
|
}
|
|
async function timersMany(n: number): Promise<string> {
|
|
// n concurrent timers with spread-out delays (0..19 ms), fired in delay order.
|
|
let c = 0;
|
|
let last = -1;
|
|
let ordered = true;
|
|
await new Promise<void>((resolve) => {
|
|
for (let i = 0; i < n; i++) {
|
|
const d = (i * 7) % 20;
|
|
setTimeout(() => {
|
|
if (d < last) ordered = false;
|
|
last = d;
|
|
c++;
|
|
if (c === n) resolve();
|
|
}, d);
|
|
}
|
|
});
|
|
return `${c} ${ordered}`;
|
|
}
|
|
async function timersCancel(n: number): Promise<string> {
|
|
let fired = 0;
|
|
const hs = [setTimeout(() => fired++, 50)];
|
|
for (let i = 1; i < n; i++) hs.push(setTimeout(() => fired++, 50 + (i % 10)));
|
|
for (let i = 0; i < hs.length; i++) clearTimeout(hs[i]!);
|
|
await new Promise<void>((r) => setTimeout(r, 1));
|
|
return `${fired}`;
|
|
}
|
|
async function immediates(n: number): Promise<string> {
|
|
let c = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
await new Promise<void>((r) => setImmediate(r));
|
|
c++;
|
|
}
|
|
return `${c}`;
|
|
}
|
|
async function microtasks(n: number): Promise<string> {
|
|
let c = 0;
|
|
await new Promise<void>((resolve) => {
|
|
const tick = (): void => {
|
|
c++;
|
|
if (c < n) queueMicrotask(tick);
|
|
else resolve();
|
|
};
|
|
queueMicrotask(tick);
|
|
});
|
|
return `${c}`;
|
|
}
|
|
async function nextTicks(n: number): Promise<string> {
|
|
let c = 0;
|
|
await new Promise<void>((resolve) => {
|
|
const tick = (): void => {
|
|
c++;
|
|
if (c < n) process.nextTick(tick);
|
|
else resolve();
|
|
};
|
|
process.nextTick(tick);
|
|
});
|
|
return `${c}`;
|
|
}
|
|
function consoleLog(n: number): string {
|
|
for (let i = 0; i < n; i++) console.log("line " + i);
|
|
return "done";
|
|
}
|
|
function consoleLogMulti(n: number): string {
|
|
for (let i = 0; i < n; i++) console.log("row", i, i * 2, true);
|
|
return "done";
|
|
}
|
|
function stdoutWrite(n: number): string {
|
|
for (let i = 0; i < n; i++) process.stdout.write("w" + i + "\n");
|
|
return "done";
|
|
}
|
|
function consoleError(n: number): string {
|
|
for (let i = 0; i < n; i++) console.error("err " + i);
|
|
return "done";
|
|
}
|
|
function fsSmall(n: number): string {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "probe-"));
|
|
for (let i = 0; i < n; i++) fs.writeFileSync(path.join(dir, "f" + i + ".txt"), "content " + i);
|
|
let t = 0;
|
|
for (let i = 0; i < n; i++) t += fs.readFileSync(path.join(dir, "f" + i + ".txt"), "utf8").length;
|
|
const names = fs.readdirSync(dir);
|
|
let e = 0;
|
|
for (let i = 0; i < n; i++) if (fs.existsSync(path.join(dir, "f" + i + ".txt"))) e++;
|
|
for (const nm of names) fs.unlinkSync(path.join(dir, nm));
|
|
fs.rmdirSync(dir);
|
|
return `${t} ${names.length} ${e}`;
|
|
}
|
|
function fsAppend(n: number): string {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "probe-"));
|
|
const f = path.join(dir, "log.txt");
|
|
for (let i = 0; i < n; i++) fs.appendFileSync(f, "entry " + i + "\n");
|
|
const len = fs.readFileSync(f, "utf8").length;
|
|
fs.unlinkSync(f);
|
|
fs.rmdirSync(dir);
|
|
return `${len}`;
|
|
}
|
|
function fsBigReadLines(n: number): string {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "probe-"));
|
|
const f = path.join(dir, "big.txt");
|
|
const lines: string[] = [];
|
|
for (let i = 0; i < n; i++) lines.push(`${i},item${i},${i % 13}`);
|
|
fs.writeFileSync(f, lines.join("\n"));
|
|
const text = fs.readFileSync(f, "utf8");
|
|
let s = 0;
|
|
for (const line of text.split("\n")) s += Number(line.slice(line.lastIndexOf(",") + 1));
|
|
fs.unlinkSync(f);
|
|
fs.rmdirSync(dir);
|
|
return `${s}`;
|
|
}
|
|
function bufferOps(n: number): string {
|
|
let t = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
const b = Buffer.from("hello " + i, "utf8");
|
|
t += b.length + b.toString("hex").length + b.toString("base64").length;
|
|
}
|
|
return `${t}`;
|
|
}
|
|
|
|
const which = process.argv[2] ?? "";
|
|
const n = Number(process.argv[3] ?? "1000");
|
|
const t0 = performance.now();
|
|
let out = "";
|
|
switch (which) {
|
|
case "await-chain": out = await awaitChain(n); break;
|
|
case "async-calls": out = await asyncCalls(n); break;
|
|
case "promise-all": out = await promiseAll(n); break;
|
|
case "then-chain": out = await thenChain(n); break;
|
|
case "timers-seq": out = await timersSeq(n); break;
|
|
case "timers-many": out = await timersMany(n); break;
|
|
case "timers-cancel": out = await timersCancel(n); break;
|
|
case "immediates": out = await immediates(n); break;
|
|
case "microtasks": out = await microtasks(n); break;
|
|
case "next-ticks": out = await nextTicks(n); break;
|
|
case "console-log": out = consoleLog(n); break;
|
|
case "console-log-multi": out = consoleLogMulti(n); break;
|
|
case "stdout-write": out = stdoutWrite(n); break;
|
|
case "console-error": out = consoleError(n); break;
|
|
case "fs-small": out = fsSmall(n); break;
|
|
case "fs-append": out = fsAppend(n); break;
|
|
case "fs-big-read": out = fsBigReadLines(n); break;
|
|
case "buffer-ops": out = bufferOps(n); break;
|
|
default: out = "unknown case";
|
|
}
|
|
const t1 = performance.now();
|
|
console.log(which, n, out);
|
|
console.error("T=" + (t1 - t0).toFixed(3));
|