From 6652ddb5d8d92df9cc63c9a39492768786b7c958 Mon Sep 17 00:00:00 2001 From: carlos-alm Date: Tue, 18 Aug 2026 22:15:27 -0600 Subject: [PATCH] fix(bench): give benchmark.ts's Full build metric a warmup-then-median too scripts/benchmark.ts's buildTimeMs (feeding BUILD-BENCHMARKS.md) measured a single cold buildGraph call with no warmup and no median at all -- a worse instance of the exact class of bug #2551 already fixed in the sibling incremental-benchmark.ts's own fullBuildMs (feeding INCREMENTAL-BENCHMARKS.md). Full build is the first buildGraph call in this worker process's lifetime, so it's the metric most exposed to one-time NAPI/rusqlite/OS-page-cache cold-start cost. Mirrors incremental-benchmark.ts's methodology exactly: 2 warmup runs (deleting the DB before each, matching a real full build), then a median of 5 timed runs via timeMedianWithValue, which also preserves buildResult for the phases breakdown further down. Verified by running the script directly against this repo for both engines -- valid buildTimeMs/perFile/phases output for each. docs check acknowledged --- scripts/benchmark.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/scripts/benchmark.ts b/scripts/benchmark.ts index afb7ac841..45d610eb1 100644 --- a/scripts/benchmark.ts +++ b/scripts/benchmark.ts @@ -12,7 +12,6 @@ import fs from 'node:fs'; import path from 'node:path'; -import { performance } from 'node:perf_hooks'; import { fileURLToPath } from 'node:url'; import { resolveBenchmarkExcludes, resolveBenchmarkSource, srcImport } from './lib/bench-config.js'; import { isWorker, workerEngine, workerTargets, forkEngines } from './lib/fork-engine.js'; @@ -92,6 +91,7 @@ try { } catch { /* older release — no worker pool to dispose */ } const WARMUP_RUNS = 2; +const BUILD_RUNS = 5; const INCREMENTAL_RUNS = 5; const QUERY_RUNS = 5; const QUERY_WARMUP_RUNS = 3; @@ -102,12 +102,21 @@ const BENCH_EXCLUDE = [...resolveBenchmarkExcludes()]; const origLog = console.log; console.log = (...args) => console.error(...args); -// Clean DB for a full build -if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath); - -const buildStart = performance.now(); -const buildResult = await buildGraph(root, { engine, incremental: false, exclude: BENCH_EXCLUDE }); -const buildTimeMs = performance.now() - buildStart; +// Full build (delete DB first each run). Warmup-then-median, mirroring +// incremental-benchmark.ts's own fullBuildMs methodology (#2436) — a single +// cold call conflates steady-state build cost with the one-time NAPI/rusqlite +// static-init cost that can only ever land on a build's very first call in +// this worker's process lifetime (#2549). +for (let i = 0; i < WARMUP_RUNS; i++) { + if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath); + await buildGraph(root, { engine, incremental: false, exclude: BENCH_EXCLUDE }); +} +const fullBuildMedian = await timeMedianWithValue(async () => { + if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath); + return await buildGraph(root, { engine, incremental: false, exclude: BENCH_EXCLUDE }); +}, BUILD_RUNS); +const buildTimeMs = fullBuildMedian.ms; +const buildResult = fullBuildMedian.value; // Warmed median of QUERY_RUNS samples with `noTests: true` to match the // methodology used by query-benchmark.ts and the per-target `queries.*Ms`