check:mst-clustering times its own CPU against a 1,500 ms budget, so a loaded box cannot fail it

gates:post runs several checks at once, and a Date.now() budget measured the neighbours too: 723 ms of work failed the 3,000 ms budget once. The budget is now the process's own CPU (process.cpuUsage, user + system); the output says "CPU" and prints the wall time beside it as not budgeted.

The budget moves to 1,500 ms, near the log-midpoint of the two builds measured here: the util takes 0.5 to 0.6 s of CPU, and the pre-924a185aa all-pairs path 4.4 to 6.9 s. Each side keeps about 2.5x, so a CPU 1.5x faster or slower still tells them apart.

Two-sided, on the 8-core box under load: 24 copies at once all PASS (CPU mean 464 ms, max 625 ms, while their wall time reached 4,076 ms); the previous Date.now() check under the same load: 18 of 24 FAIL. Against the pre-924a185aa MSTClusteringUtil it FAILS: 4,418 to 6,921 ms of CPU across four runs.
This commit is contained in:
Doxa Maps 2026-09-29 07:04:12 +00:00
parent e34fac45d4
commit 16f2d51531

View file

@ -9,7 +9,16 @@
* MSTClusteringUtil now runs Prim in O(n²). * MSTClusteringUtil now runs Prim in O(n²).
* *
* ASSERTS (1) on sizes 2-400, capped and uncapped, the util's tree has the SAME edge count and total * ASSERTS (1) on sizes 2-400, capped and uncapped, the util's tree has the SAME edge count and total
* length as a reference all-pairs Kruskal written here; (2) 2,107 pins in under 3 s. * length as a reference all-pairs Kruskal written here; (2) 2,107 pins in under 1.5 s of CPU.
*
* ★ CPU TIME, NOT WALL TIME (2026-09-29). gates:post runs several checks at once on a shared box, and a
* Date.now() budget measured the neighbours as well: 723 ms of work failed a 3,000 ms budget once. The
* budget is now this process's own CPU (process.cpuUsage, user + system), which the load does not
* inflate. Measured on a loaded 8-core box, the util takes about 0.5 s of CPU (24 copies at once: max
* 0.6 s, against 5.3 s of wall), and the pre-924a185aa all-pairs path 4.4 to 6.9 s. The budget sits near
* the log-midpoint of the two, 1,500 ms, so each side keeps a margin of about 2.5x (0.6 s is 2.4x under
* it, 4.4 s is 2.9x over): a CPU 1.5x faster (a laptop, a CI runner) cannot let the old path pass, and
* one 1.5x slower cannot fail the new path.
* EXIT 0 pass · 1 fail * EXIT 0 pass · 1 fail
*/ */
import MST from '../library/utils/MSTClusteringUtil.js' import MST from '../library/utils/MSTClusteringUtil.js'
@ -39,11 +48,12 @@ for (const [n, cap] of [[2, Infinity], [3, Infinity], [50, Infinity], [400, Infi
console.log(` ${ok ? 'ok ' : 'FAIL'} ${String(n).padStart(4)} pins, cap ${cap}: ${L.length} edges, length ${len.toFixed(4)} (reference ${ref.n}, ${ref.len.toFixed(4)})`) console.log(` ${ok ? 'ok ' : 'FAIL'} ${String(n).padStart(4)} pins, cap ${cap}: ${L.length} edges, length ${len.toFixed(4)} (reference ${ref.n}, ${ref.len.toFixed(4)})`)
} }
console.warn = quiet console.warn = quiet
const t = Date.now() const t = Date.now(), cpu0 = process.cpuUsage()
const L = new MST({}).generateAllMSTLines({ A: pts(1500, 1.1), B: pts(607, 0.9) }) const L = new MST({}).generateAllMSTLines({ A: pts(1500, 1.1), B: pts(607, 0.9) })
const ms = Date.now() - t const cpu = process.cpuUsage(cpu0), ms = Math.round((cpu.user + cpu.system) / 1000), wall = Date.now() - t
const fast = ms < 3000 && L.length === 2105 const BUDGET_CPU_MS = 1500
const fast = ms < BUDGET_CPU_MS && L.length === 2105
if (!fast) bad++ if (!fast) bad++
console.log(` ${fast ? 'ok ' : 'FAIL'} 2,107 pins in two groups: ${L.length} lines in ${ms} ms (budget 3000 ms)`) console.log(` ${fast ? 'ok ' : 'FAIL'} 2,107 pins in two groups: ${L.length} lines in ${ms} ms CPU (budget ${BUDGET_CPU_MS} ms CPU; wall ${wall} ms, not budgeted)`)
if (bad) { console.log(`MST-CLUSTERING: FAIL — ${bad} case(s).`); process.exit(1) } if (bad) { console.log(`MST-CLUSTERING: FAIL — ${bad} case(s).`); process.exit(1) }
console.log('MST-CLUSTERING: PASS — a true minimum spanning tree/forest, and fast enough for the main thread.') console.log('MST-CLUSTERING: PASS — a true minimum spanning tree/forest, and fast enough for the main thread.')