fix/address flaky wall clock based test by doing best of 3

This commit is contained in:
M.Schmidt
2026-07-17 00:06:53 +02:00
parent 6215f37bf6
commit 494dcb8486
@@ -24,6 +24,7 @@ describe('worker-pool hashing (architecture gate)', () => {
// on the main thread, serially. // on the main thread, serially.
const nFiles = 24; const nFiles = 24;
const size = 4 * 1024 * 1024; const size = 4 * 1024 * 1024;
const trials = 3;
const dir = await fs.mkdtemp(join(tmpdir(), 'hashbench-')); const dir = await fs.mkdtemp(join(tmpdir(), 'hashbench-'));
const paths: string[] = []; const paths: string[] = [];
for (let i = 0; i < nFiles; i++) { for (let i = 0; i < nFiles; i++) {
@@ -35,12 +36,14 @@ describe('worker-pool hashing (architecture gate)', () => {
} }
// Sequential (old): read + hash on the calling thread. // Sequential (old): read + hash on the calling thread.
const t0 = performance.now(); const runSequential = async () => {
for (const p of paths) { const t0 = performance.now();
const b = await fs.readFile(p); for (const p of paths) {
createHash('sha256').update(b).digest('hex'); const b = await fs.readFile(p);
} createHash('sha256').update(b).digest('hex');
const seqMs = performance.now() - t0; }
return performance.now() - t0;
};
// 3-lane pool (new): each worker reads + hashes its own files. // 3-lane pool (new): each worker reads + hashes its own files.
const lanes = 3; const lanes = 3;
@@ -53,35 +56,52 @@ describe('worker-pool hashing (architecture gate)', () => {
parentPort.postMessage(createHash('sha256').update(b).digest('hex')); parentPort.postMessage(createHash('sha256').update(b).digest('hex'));
}); });
`; `;
const workers = Array.from({ length: lanes }, () => new Worker(workerSrc, { eval: true })); const runPooled = async () => {
let next = 0; const workers = Array.from({ length: lanes }, () => new Worker(workerSrc, { eval: true }));
const t1 = performance.now(); let next = 0;
await Promise.all( const t1 = performance.now();
workers.map( await Promise.all(
(w) => workers.map(
new Promise<void>((resolve, reject) => { (w) =>
const feed = () => { new Promise<void>((resolve, reject) => {
if (next >= paths.length) { const feed = () => {
resolve(); if (next >= paths.length) {
return; resolve();
} return;
const i = next++; }
w.once('message', () => feed()); const i = next++;
w.once('error', reject); w.once('message', () => feed());
w.postMessage(paths[i]); w.once('error', reject);
}; w.postMessage(paths[i]);
feed(); };
}) feed();
) })
); )
const poolMs = performance.now() - t1; );
await Promise.all(workers.map((w) => w.terminate())); const ms = performance.now() - t1;
await Promise.all(workers.map((w) => w.terminate()));
return ms;
};
// Best-of-`trials` wall-clock per strategy: a single sample is prone
// to scheduler/GC noise on a loaded machine, which can tip either
// side when the two are close. Noise only ever adds delay, so the
// minimum across trials is each strategy's true achievable time —
// a genuine architecture regression still fails every trial.
const seqTimes: number[] = [];
const poolTimes: number[] = [];
for (let i = 0; i < trials; i++) {
seqTimes.push(await runSequential());
poolTimes.push(await runPooled());
}
const seqMs = Math.min(...seqTimes);
const poolMs = Math.min(...poolTimes);
await fs.rm(dir, { recursive: true, force: true }); await fs.rm(dir, { recursive: true, force: true });
// eslint-disable-next-line no-console
console.info( console.info(
`read+hash ${nFiles} x 4 MiB: sequential ${seqMs.toFixed(0)} ms vs 3-lane pool ${poolMs.toFixed(0)} ms (${(seqMs / poolMs).toFixed(1)}x)` `read+hash ${nFiles} x 4 MiB over ${trials} trials: best sequential ${seqMs.toFixed(0)} ms vs best 3-lane pool ${poolMs.toFixed(0)} ms (${(seqMs / poolMs).toFixed(1)}x)`
); );
expect(poolMs).toBeLessThan(seqMs); expect(poolMs).toBeLessThan(seqMs);
}); }, 20000);
}); });