repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/firefox-bidi/__bench__/runBatch.bench.mts
Raw// Microbenchmark: batch-of-25 frames (22 small results + 3 x 512KB results)
// through the batch-processing hot path, plus final payload serialization.
// Usage: npx tsx bench/runBatch.bench.mts [iterations]
// Prints JSON: { iterations, samples_ms, stats }
import { performance } from "node:perf_hooks";
import { runBatchBaseline, payloadBaseline, makeFixture } from "./baseline.ts";
import { runBatch, assemblePayload, type BidiResponse, type Frame, type BatchCtx } from "../lib.ts";
const ITER = Number(process.argv[2] ?? 15);
function stats(samples: number[]) {
const sorted = [...samples].sort((a, b) => a - b);
const mean = samples.reduce((a, b) => a + b, 0) / samples.length;
const sd = Math.sqrt(samples.reduce((a, b) => a + (b - mean) ** 2, 0) / samples.length);
return { n: samples.length, median: sorted[Math.floor(sorted.length / 2)], mean, sd, min: sorted[0], max: sorted[sorted.length - 1], p90: sorted[Math.floor(sorted.length * 0.9)] };
}
// Warmup: JIT + fs settle
{
const fx = await makeFixture();
await runBatchBaseline(fx.ws, fx.ctx, fx.frames, fx.byId, 30000);
const fx2 = await makeFixture();
await runBatch(fx2.ws, fx2.ctx, fx2.frames as Frame[], fx2.byId, 30000);
}
// Interleaved A/B pairs cancel machine drift; per-pair differences feed the
// signed-rank test. Fixture construction (fs mkdir) stays outside the timer.
const pairs: { base: number; cand: number }[] = [];
for (let i = 0; i < ITER; i++) {
const fx = await makeFixture();
const t0 = performance.now();
const base = await runBatchBaseline(fx.ws, fx.ctx, fx.frames, fx.byId, 30000);
const baseText = payloadBaseline({ sessionId: "s", port: 9222, capabilities: {} }, base, [], undefined, 200);
pairs.push({ base: performance.now() - t0, cand: 0 });
const fx2 = await makeFixture();
const t1 = performance.now();
const cand = await runBatch(fx2.ws, fx2.ctx, fx2.frames as Frame[], fx2.byId, 30000);
const candText = assemblePayload({ sessionId: "s", port: 9222, capabilities: {} }, cand.texts, [], 200, undefined);
pairs[i].cand = performance.now() - t1;
if (i === 0) {
const norm = (t: string, dir: string) => JSON.stringify(JSON.parse(t.replaceAll(dir, "<SPOOL>")));
if (norm(baseText, fx.ctx.spoolDir) !== norm(candText, fx2.ctx.spoolDir)) {
console.error("OUTPUT MISMATCH between baseline and candidate");
process.exit(1);
}
}
}
const baseSamples = pairs.map((p) => p.base);
const candSamples = pairs.map((p) => p.cand);
const diffs = pairs.map((p) => p.base - p.cand);
// Wilcoxon signed-rank (normal approximation, no zero-diff correction)
function wilcoxon(d: number[]): number {
const nz = d.filter((x) => x !== 0).map((x, i) => ({ abs: Math.abs(x), sign: Math.sign(x), rank: 0 }));
nz.sort((a, b) => a.abs - b.abs);
nz.forEach((o, i) => (o.rank = i + 1));
const wPlus = nz.filter((o) => o.sign > 0).reduce((a, o) => a + o.rank, 0);
const n = nz.length;
const mu = (n * (n + 1)) / 4;
const sigma = Math.sqrt((n * (n + 1) * (2 * n + 1)) / 24);
const z = (wPlus - mu) / sigma;
const phi = 0.5 * (1 + erf(Math.abs(z) / Math.SQRT2));
return 2 * (1 - phi);
}
function erf(x: number): number {
const t = 1 / (1 + 0.3275911 * Math.abs(x));
const y = 1 - ((((1.061405429 * t - 1.453152027) * t + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * Math.exp(-x * x);
return x >= 0 ? y : -y;
}
console.log(JSON.stringify({
iterations: ITER,
samples_ms: { baseline: baseSamples, candidate: candSamples },
paired_diff_ms: { mean: diffs.reduce((a, b) => a + b, 0) / diffs.length },
stats: { baseline: stats(baseSamples), candidate: stats(candSamples) },
wilcoxon_p: wilcoxon(diffs),
output_equal: true,
}));