* Pool size from CPU headroom, memory headroom, and the explicit override. * Pure — every input injected — so the whole matrix is unit-testable. * * CPU term: `availableParallelism` (cpuset/affinity-honest — `os.cpus()` * enumerates the host's CPUs and sized SIX workers inside a 2-CPU cp
(opts: {
explicit?: string;
availableParallelism: number;
memoryBudget: number;
dbSizeBytes: number;
})
| 84 | * cost — callers get null and stay sequential. |
| 85 | */ |
| 86 | static resolvePoolSize(opts: { |
| 87 | explicit?: string; |
| 88 | availableParallelism: number; |
| 89 | memoryBudget: number; |
| 90 | dbSizeBytes: number; |
| 91 | }): number | null { |
| 92 | if (opts.explicit !== undefined && opts.explicit !== '') { |
| 93 | const n = Number.parseInt(opts.explicit, 10); |
| 94 | if (Number.isFinite(n)) { |
| 95 | if (n <= 0) return null; |
| 96 | return Math.min(n, 16); |
| 97 | } |
| 98 | } |
| 99 | // No floor: at ap=2 the pool LOSES to sequential outright — measured on |
| 100 | // the kernel-scale 2-cpuset envelope: resolution 853s sequential vs |
| 101 | // 1,150s pooled-6-on-2 (§7a.1), and synthesis is Amdahl-bound by its |
| 102 | // dominant pass (cFnPtrEdges 306s of 358s) so pooling it bought nothing. |
| 103 | // ap−1 < 2 ⇒ sequential is the fast path, not a fallback. |
| 104 | const cpuCap = Math.min(opts.availableParallelism - 1, 6); |
| 105 | const perWorker = Math.min(Math.max(opts.dbSizeBytes * 0.2, 256 * 1024 * 1024), 1.5 * 1024 * 1024 * 1024); |
| 106 | const memCap = Math.floor((opts.memoryBudget * 0.7) / perWorker); |
| 107 | const size = Math.min(cpuCap, memCap); |
| 108 | return size >= 2 ? size : null; |
| 109 | } |
| 110 | |
| 111 | /** |
| 112 | * Create a pool when the compiled worker exists (absent when running from |