benchforge 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,14 @@
1
- import fs, { mkdir, readFile, writeFile } from "node:fs/promises";
1
+ import { a as BasicRunner, c as createAdaptiveWrapper, d as bootstrapDifferenceCI, f as discoverVariants, i as createRunner, l as msToNs, n as getElapsed, o as computeStats, r as getPerfNow, s as checkConvergence, t as debugWorkerTiming, u as average } from "./TimingUtils-D4z1jpp2.mjs";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
3
  import { fileURLToPath } from "node:url";
3
- import { getHeapStatistics } from "node:v8";
4
4
  import { execSync, fork, spawn } from "node:child_process";
5
+ import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
5
6
  import path, { dirname, extname, join, resolve } from "node:path";
6
7
  import pico from "picocolors";
7
8
  import { table } from "table";
8
9
  import yargs from "yargs";
9
10
  import { hideBin } from "yargs/helpers";
10
11
  import { chromium } from "playwright";
11
- import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
12
12
  import { createServer } from "node:http";
13
13
  import open from "open";
14
14
 
@@ -30,583 +30,6 @@ async function loadCaseData(casesModule, caseId) {
30
30
  return { data: caseId };
31
31
  }
32
32
 
33
- //#endregion
34
- //#region src/matrix/VariantLoader.ts
35
- /** Discover variant ids from a directory of .ts files */
36
- async function discoverVariants(dirUrl) {
37
- const dirPath = fileURLToPath(dirUrl);
38
- return (await fs.readdir(dirPath, { withFileTypes: true })).filter((e) => e.isFile() && e.name.endsWith(".ts")).map((e) => e.name.slice(0, -3)).sort();
39
- }
40
-
41
- //#endregion
42
- //#region src/StatisticalUtils.ts
43
- const bootstrapSamples = 1e4;
44
- const confidence = .95;
45
- /** @return relative standard deviation (coefficient of variation) */
46
- function coefficientOfVariation(samples) {
47
- const mean = average(samples);
48
- if (mean === 0) return 0;
49
- return standardDeviation(samples) / mean;
50
- }
51
- /** @return median absolute deviation for robust variability measure */
52
- function medianAbsoluteDeviation(samples) {
53
- const median = percentile$1(samples, .5);
54
- return percentile$1(samples.map((x) => Math.abs(x - median)), .5);
55
- }
56
- /** @return mean of values */
57
- function average(values) {
58
- return values.reduce((a, b) => a + b, 0) / values.length;
59
- }
60
- /** @return standard deviation with Bessel's correction */
61
- function standardDeviation(samples) {
62
- if (samples.length <= 1) return 0;
63
- const mean = average(samples);
64
- const variance = samples.reduce((sum, x) => sum + (x - mean) ** 2, 0) / (samples.length - 1);
65
- return Math.sqrt(variance);
66
- }
67
- /** @return value at percentile p (0-1) */
68
- function percentile$1(values, p) {
69
- const sorted = [...values].sort((a, b) => a - b);
70
- const index = Math.ceil(sorted.length * p) - 1;
71
- return sorted[Math.max(0, index)];
72
- }
73
- /** @return bootstrap resample with replacement */
74
- function createResample(samples) {
75
- const n = samples.length;
76
- const rand = () => samples[Math.floor(Math.random() * n)];
77
- return Array.from({ length: n }, rand);
78
- }
79
- /** @return confidence interval [lower, upper] */
80
- function computeInterval(medians, confidence) {
81
- const alpha = (1 - confidence) / 2;
82
- return [percentile$1(medians, alpha), percentile$1(medians, 1 - alpha)];
83
- }
84
- /** Bin values into histogram for compact visualization */
85
- function binValues(values, binCount = 30) {
86
- const sorted = [...values].sort((a, b) => a - b);
87
- const min = sorted[0];
88
- const max = sorted[sorted.length - 1];
89
- if (min === max) return [{
90
- x: min,
91
- count: values.length
92
- }];
93
- const step = (max - min) / binCount;
94
- const counts = new Array(binCount).fill(0);
95
- for (const v of values) {
96
- const bin = Math.min(Math.floor((v - min) / step), binCount - 1);
97
- counts[bin]++;
98
- }
99
- return counts.map((count, i) => ({
100
- x: min + (i + .5) * step,
101
- count
102
- }));
103
- }
104
- /** @return bootstrap CI for percentage difference between baseline and current medians */
105
- function bootstrapDifferenceCI(baseline, current, options = {}) {
106
- const { resamples = bootstrapSamples, confidence: conf = confidence } = options;
107
- const baselineMedian = percentile$1(baseline, .5);
108
- const observedPercent = (percentile$1(current, .5) - baselineMedian) / baselineMedian * 100;
109
- const diffs = [];
110
- for (let i = 0; i < resamples; i++) {
111
- const resB = createResample(baseline);
112
- const resC = createResample(current);
113
- const medB = percentile$1(resB, .5);
114
- const medC = percentile$1(resC, .5);
115
- diffs.push((medC - medB) / medB * 100);
116
- }
117
- const ci = computeInterval(diffs, conf);
118
- const excludesZero = ci[0] > 0 || ci[1] < 0;
119
- let direction = "uncertain";
120
- if (excludesZero) direction = observedPercent < 0 ? "faster" : "slower";
121
- const histogram = binValues(diffs);
122
- return {
123
- percent: observedPercent,
124
- ci,
125
- direction,
126
- histogram
127
- };
128
- }
129
-
130
- //#endregion
131
- //#region src/runners/RunnerUtils.ts
132
- const msToNs = 1e6;
133
-
134
- //#endregion
135
- //#region src/runners/AdaptiveWrapper.ts
136
- const minTime = 1e3;
137
- const maxTime = 1e4;
138
- const targetConfidence = 95;
139
- const fallbackThreshold = 80;
140
- const windowSize = 50;
141
- const stability = .05;
142
- const initialBatch = 100;
143
- const continueBatch = 100;
144
- const continueIterations = 10;
145
- /** @return adaptive sampling runner wrapper */
146
- function createAdaptiveWrapper(baseRunner, options) {
147
- return { async runBench(benchmark, runnerOptions, params) {
148
- return runAdaptiveBench(baseRunner, benchmark, runnerOptions, options, params);
149
- } };
150
- }
151
- /** @return results using adaptive sampling strategy */
152
- async function runAdaptiveBench(baseRunner, benchmark, runnerOptions, options, params) {
153
- const { minTime: min = options.minTime ?? minTime, maxTime: max = options.maxTime ?? maxTime, targetConfidence: target = options.convergence ?? targetConfidence } = runnerOptions;
154
- const allSamples = [];
155
- const warmup = await collectInitial(baseRunner, benchmark, runnerOptions, params, allSamples);
156
- const startTime = performance.now();
157
- await collectAdaptive(baseRunner, benchmark, runnerOptions, params, allSamples, {
158
- minTime: min,
159
- maxTime: max,
160
- targetConfidence: target,
161
- startTime
162
- });
163
- return buildResults(allSamples, startTime, checkConvergence(allSamples.map((s) => s * msToNs)), benchmark.name, warmup);
164
- }
165
- /** @return warmupSamples from initial batch */
166
- async function collectInitial(baseRunner, benchmark, runnerOptions, params, allSamples) {
167
- const opts = {
168
- ...runnerOptions,
169
- maxTime: initialBatch,
170
- maxIterations: void 0
171
- };
172
- const results = await baseRunner.runBench(benchmark, opts, params);
173
- appendSamples(results[0], allSamples);
174
- return results[0].warmupSamples;
175
- }
176
- /** @return samples until convergence or timeout */
177
- async function collectAdaptive(baseRunner, benchmark, runnerOptions, params, allSamples, limits) {
178
- const { minTime, maxTime, targetConfidence, startTime } = limits;
179
- let lastLog = 0;
180
- while (performance.now() - startTime < maxTime) {
181
- const convergence = checkConvergence(allSamples.map((s) => s * msToNs));
182
- const elapsed = performance.now() - startTime;
183
- if (elapsed - lastLog > 1e3) {
184
- const elapsedSec = (elapsed / 1e3).toFixed(1);
185
- const conf = convergence.confidence.toFixed(0);
186
- process.stderr.write(`\r◊ ${benchmark.name}: ${conf}% confident (${elapsedSec}s) `);
187
- lastLog = elapsed;
188
- }
189
- if (shouldStop(convergence, targetConfidence, elapsed, minTime)) break;
190
- const opts = {
191
- ...runnerOptions,
192
- maxTime: continueBatch,
193
- maxIterations: continueIterations,
194
- skipWarmup: true
195
- };
196
- appendSamples((await baseRunner.runBench(benchmark, opts, params))[0], allSamples);
197
- }
198
- process.stderr.write("\r" + " ".repeat(60) + "\r");
199
- }
200
- /** Append samples one-by-one to avoid stack overflow from spread on large arrays */
201
- function appendSamples(result, samples) {
202
- if (!result.samples?.length) return;
203
- for (const sample of result.samples) samples.push(sample);
204
- }
205
- /** @return true if convergence reached or timeout */
206
- function shouldStop(convergence, targetConfidence, elapsedTime, minTime) {
207
- if (convergence.converged && convergence.confidence >= targetConfidence) return true;
208
- const threshold = Math.max(targetConfidence, fallbackThreshold);
209
- return elapsedTime >= minTime && convergence.confidence >= threshold;
210
- }
211
- /** @return measured results with convergence metrics */
212
- function buildResults(samplesMs, startTime, convergence, name, warmupSamples) {
213
- const totalTime = (performance.now() - startTime) / 1e3;
214
- return [{
215
- name,
216
- samples: samplesMs,
217
- warmupSamples,
218
- time: computeTimeStats(samplesMs.map((s) => s * msToNs)),
219
- totalTime,
220
- convergence
221
- }];
222
- }
223
- /** @return time percentiles and statistics in ms */
224
- function computeTimeStats(samplesNs) {
225
- const samplesMs = samplesNs.map((s) => s / msToNs);
226
- const { min, max, sum } = getMinMaxSum(samplesNs);
227
- const percentiles = getPercentiles(samplesNs);
228
- const robust = getRobustMetrics(samplesMs);
229
- return {
230
- min: min / msToNs,
231
- max: max / msToNs,
232
- avg: sum / samplesNs.length / msToNs,
233
- ...percentiles,
234
- ...robust
235
- };
236
- }
237
- /** @return min, max, sum of samples */
238
- function getMinMaxSum(samples) {
239
- return {
240
- min: samples.reduce((a, b) => Math.min(a, b), Number.POSITIVE_INFINITY),
241
- max: samples.reduce((a, b) => Math.max(a, b), Number.NEGATIVE_INFINITY),
242
- sum: samples.reduce((a, b) => a + b, 0)
243
- };
244
- }
245
- /** @return percentiles in ms */
246
- function getPercentiles(samples) {
247
- return {
248
- p25: percentile$1(samples, .25) / msToNs,
249
- p50: percentile$1(samples, .5) / msToNs,
250
- p75: percentile$1(samples, .75) / msToNs,
251
- p95: percentile$1(samples, .95) / msToNs,
252
- p99: percentile$1(samples, .99) / msToNs,
253
- p999: percentile$1(samples, .999) / msToNs
254
- };
255
- }
256
- /** @return robust variability metrics */
257
- function getRobustMetrics(samplesMs) {
258
- const impact = getOutlierImpact(samplesMs);
259
- return {
260
- cv: coefficientOfVariation(samplesMs),
261
- mad: medianAbsoluteDeviation(samplesMs),
262
- outlierRate: impact.ratio
263
- };
264
- }
265
- /** @return outlier impact as proportion of total time */
266
- function getOutlierImpact(samples) {
267
- if (samples.length === 0) return {
268
- ratio: 0,
269
- count: 0
270
- };
271
- const median = percentile$1(samples, .5);
272
- const threshold = median + 1.5 * (percentile$1(samples, .75) - median);
273
- let excessTime = 0;
274
- let count = 0;
275
- for (const sample of samples) if (sample > threshold) {
276
- excessTime += sample - median;
277
- count++;
278
- }
279
- const totalTime = samples.reduce((a, b) => a + b, 0);
280
- return {
281
- ratio: totalTime > 0 ? excessTime / totalTime : 0,
282
- count
283
- };
284
- }
285
- /** @return convergence based on window stability */
286
- function checkConvergence(samples) {
287
- const windowSize = getWindowSize(samples);
288
- const minSamples = windowSize * 2;
289
- if (samples.length < minSamples) return buildProgressResult(samples.length, minSamples);
290
- return buildConvergence(getStability(samples, windowSize));
291
- }
292
- /** @return progress when samples insufficient */
293
- function buildProgressResult(currentSamples, minSamples) {
294
- return {
295
- converged: false,
296
- confidence: currentSamples / minSamples * 100,
297
- reason: `Collecting samples: ${currentSamples}/${minSamples}`
298
- };
299
- }
300
- /** @return stability metrics between windows */
301
- function getStability(samples, windowSize) {
302
- const recent = samples.slice(-windowSize);
303
- const previous = samples.slice(-windowSize * 2, -windowSize);
304
- const recentMs = recent.map((s) => s / msToNs);
305
- const previousMs = previous.map((s) => s / msToNs);
306
- const medianRecent = percentile$1(recentMs, .5);
307
- const medianPrevious = percentile$1(previousMs, .5);
308
- const medianDrift = Math.abs(medianRecent - medianPrevious) / medianPrevious;
309
- const impactRecent = getOutlierImpact(recentMs);
310
- const impactPrevious = getOutlierImpact(previousMs);
311
- const impactDrift = Math.abs(impactRecent.ratio - impactPrevious.ratio);
312
- return {
313
- medianDrift,
314
- impactDrift,
315
- medianStable: medianDrift < stability,
316
- impactStable: impactDrift < stability
317
- };
318
- }
319
- /** @return convergence from stability metrics */
320
- function buildConvergence(metrics) {
321
- const { medianDrift, impactDrift, medianStable, impactStable } = metrics;
322
- if (medianStable && impactStable) return {
323
- converged: true,
324
- confidence: 100,
325
- reason: "Stable performance pattern"
326
- };
327
- const confidence = Math.min(100, (1 - medianDrift / stability) * 50 + (1 - impactDrift / stability) * 50);
328
- const reason = medianDrift > impactDrift ? `Median drifting: ${(medianDrift * 100).toFixed(1)}%` : `Outlier impact changing: ${(impactDrift * 100).toFixed(1)}%`;
329
- return {
330
- converged: false,
331
- confidence: Math.max(0, confidence),
332
- reason
333
- };
334
- }
335
- /** @return window size scaled to execution time */
336
- function getWindowSize(samples) {
337
- if (samples.length < 20) return windowSize;
338
- const recentMedian = percentile$1(samples.slice(-20).map((s) => s / msToNs), .5);
339
- if (recentMedian < .01) return 200;
340
- if (recentMedian < .1) return 100;
341
- if (recentMedian < 1) return 50;
342
- if (recentMedian < 10) return 30;
343
- return 20;
344
- }
345
-
346
- //#endregion
347
- //#region src/runners/BenchRunner.ts
348
- /** Execute benchmark with optional parameters */
349
- function executeBenchmark(benchmark, params) {
350
- benchmark.fn(params);
351
- }
352
-
353
- //#endregion
354
- //#region src/runners/BasicRunner.ts
355
- /**
356
- * Wait time after gc() for V8 to stabilize (ms).
357
- *
358
- * V8 has 4 compilation tiers: Ignition (interpreter) -> Sparkplug (baseline) ->
359
- * Maglev (mid-tier optimizer) -> TurboFan (full optimizer). Tiering thresholds:
360
- * - Ignition -> Sparkplug: 8 invocations
361
- * - Sparkplug -> Maglev: 500 invocations
362
- * - Maglev -> TurboFan: 6000 invocations
363
- *
364
- * Optimization compilation happens on background threads and requires idle time
365
- * on the main thread to complete. Without sufficient warmup + settle time,
366
- * benchmarks exhibit bimodal timing: slow Sparkplug samples (~30% slower) mixed
367
- * with fast optimized samples.
368
- *
369
- * The warmup iterations trigger the optimization decision, then gcSettleTime
370
- * provides idle time for background compilation to finish before measurement.
371
- *
372
- * @see https://v8.dev/blog/sparkplug
373
- * @see https://v8.dev/blog/maglev
374
- * @see https://v8.dev/blog/background-compilation
375
- */
376
- const gcSettleTime = 1e3;
377
- /** @return runner with time and iteration limits */
378
- var BasicRunner = class {
379
- async runBench(benchmark, options, params) {
380
- const collected = await collectSamples({
381
- benchmark,
382
- params,
383
- ...defaultCollectOptions,
384
- ...options
385
- });
386
- return [buildMeasuredResults(benchmark.name, collected)];
387
- }
388
- };
389
- const defaultCollectOptions = {
390
- maxTime: 5e3,
391
- maxIterations: 1e6,
392
- warmup: 0,
393
- traceOpt: false,
394
- noSettle: false
395
- };
396
- function buildMeasuredResults(name, c) {
397
- const time = computeStats(c.samples);
398
- const convergence = checkConvergence(c.samples.map((s) => s * msToNs));
399
- return {
400
- name,
401
- samples: c.samples,
402
- warmupSamples: c.warmupSamples,
403
- heapSamples: c.heapSamples,
404
- timestamps: c.timestamps,
405
- time,
406
- heapSize: {
407
- avg: c.heapGrowth,
408
- min: c.heapGrowth,
409
- max: c.heapGrowth
410
- },
411
- convergence,
412
- optStatus: c.optStatus,
413
- optSamples: c.optSamples,
414
- pausePoints: c.pausePoints
415
- };
416
- }
417
- /** @return timing samples and amortized allocation from benchmark execution */
418
- async function collectSamples(p) {
419
- if (!p.maxIterations && !p.maxTime) throw new Error(`At least one of maxIterations or maxTime must be set`);
420
- const warmupSamples = p.skipWarmup ? [] : await runWarmup(p);
421
- const heapBefore = process.memoryUsage().heapUsed;
422
- const { samples, heapSamples, timestamps, optStatuses, pausePoints } = await runSampleLoop(p);
423
- const heapGrowth = Math.max(0, process.memoryUsage().heapUsed - heapBefore) / 1024 / samples.length;
424
- if (samples.length === 0) throw new Error(`No samples collected for benchmark: ${p.benchmark.name}`);
425
- return {
426
- samples,
427
- warmupSamples,
428
- heapGrowth,
429
- heapSamples,
430
- timestamps,
431
- optStatus: p.traceOpt ? analyzeOptStatus(samples, optStatuses) : void 0,
432
- optSamples: p.traceOpt && optStatuses.length > 0 ? optStatuses : void 0,
433
- pausePoints
434
- };
435
- }
436
- /** Run warmup iterations with gc + settle time for V8 optimization */
437
- async function runWarmup(p) {
438
- const gc = gcFunction();
439
- const samples = new Array(p.warmup);
440
- for (let i = 0; i < p.warmup; i++) {
441
- const start = performance.now();
442
- executeBenchmark(p.benchmark, p.params);
443
- samples[i] = performance.now() - start;
444
- }
445
- gc();
446
- if (!p.noSettle) {
447
- await new Promise((r) => setTimeout(r, gcSettleTime));
448
- gc();
449
- }
450
- return samples;
451
- }
452
- /** Estimate sample count for pre-allocation */
453
- function estimateSampleCount(maxTime, maxIterations) {
454
- return maxIterations || Math.ceil(maxTime / .1);
455
- }
456
- /** Pre-allocate arrays to reduce GC pressure during measurement */
457
- function createSampleArrays(n, trackHeap, trackOpt) {
458
- const arr = (track) => track ? new Array(n) : [];
459
- return {
460
- samples: new Array(n),
461
- timestamps: new Array(n),
462
- heapSamples: arr(trackHeap),
463
- optStatuses: arr(trackOpt),
464
- pausePoints: []
465
- };
466
- }
467
- /** Trim arrays to actual sample count */
468
- function trimArrays(a, count, trackHeap, trackOpt) {
469
- a.samples.length = a.timestamps.length = count;
470
- if (trackHeap) a.heapSamples.length = count;
471
- if (trackOpt) a.optStatuses.length = count;
472
- }
473
- /** Collect timing samples with periodic pauses for V8 optimization */
474
- async function runSampleLoop(p) {
475
- const { maxTime, maxIterations, pauseFirst, pauseInterval = 0, pauseDuration = 100 } = p;
476
- const trackHeap = true;
477
- const getOptStatus = p.traceOpt ? createOptStatusGetter() : void 0;
478
- const a = createSampleArrays(estimateSampleCount(maxTime, maxIterations), trackHeap, !!getOptStatus);
479
- let count = 0;
480
- let elapsed = 0;
481
- let totalPauseTime = 0;
482
- const loopStart = performance.now();
483
- while ((!maxIterations || count < maxIterations) && (!maxTime || elapsed < maxTime)) {
484
- const start = performance.now();
485
- executeBenchmark(p.benchmark, p.params);
486
- const end = performance.now();
487
- a.samples[count] = end - start;
488
- a.timestamps[count] = Number(process.hrtime.bigint() / 1000n);
489
- a.heapSamples[count] = getHeapStatistics().used_heap_size;
490
- if (getOptStatus) a.optStatuses[count] = getOptStatus(p.benchmark.fn);
491
- count++;
492
- if (shouldPause(count, pauseFirst, pauseInterval)) {
493
- a.pausePoints.push({
494
- sampleIndex: count - 1,
495
- durationMs: pauseDuration
496
- });
497
- const pauseStart = performance.now();
498
- await new Promise((r) => setTimeout(r, pauseDuration));
499
- totalPauseTime += performance.now() - pauseStart;
500
- }
501
- elapsed = performance.now() - loopStart - totalPauseTime;
502
- }
503
- trimArrays(a, count, trackHeap, !!getOptStatus);
504
- return {
505
- samples: a.samples,
506
- heapSamples: a.heapSamples,
507
- timestamps: a.timestamps,
508
- optStatuses: a.optStatuses,
509
- pausePoints: a.pausePoints
510
- };
511
- }
512
- /** Check if we should pause at this iteration for V8 optimization */
513
- function shouldPause(iter, first, interval) {
514
- if (first !== void 0 && iter === first) return true;
515
- if (interval <= 0) return false;
516
- if (first === void 0) return iter % interval === 0;
517
- return (iter - first) % interval === 0;
518
- }
519
- /** @return percentiles and basic statistics */
520
- function computeStats(samples) {
521
- const sorted = [...samples].sort((a, b) => a - b);
522
- const avg = samples.reduce((sum, s) => sum + s, 0) / samples.length;
523
- return {
524
- min: sorted[0],
525
- max: sorted[sorted.length - 1],
526
- avg,
527
- p50: percentile(sorted, .5),
528
- p75: percentile(sorted, .75),
529
- p99: percentile(sorted, .99),
530
- p999: percentile(sorted, .999)
531
- };
532
- }
533
- /** @return percentile value with linear interpolation */
534
- function percentile(sortedArray, p) {
535
- const index = (sortedArray.length - 1) * p;
536
- const lower = Math.floor(index);
537
- const upper = Math.ceil(index);
538
- const weight = index % 1;
539
- if (upper >= sortedArray.length) return sortedArray[sortedArray.length - 1];
540
- return sortedArray[lower] * (1 - weight) + sortedArray[upper] * weight;
541
- }
542
- /** @return runtime gc() function, or no-op if unavailable */
543
- function gcFunction() {
544
- const gc = globalThis.gc || globalThis.__gc;
545
- if (gc) return gc;
546
- console.warn("gc() not available, run node/bun with --expose-gc");
547
- return () => {};
548
- }
549
- /** @return function to get V8 optimization status (requires --allow-natives-syntax) */
550
- function createOptStatusGetter() {
551
- try {
552
- const getter = new Function("f", "return %GetOptimizationStatus(f)");
553
- getter(() => {});
554
- return getter;
555
- } catch {
556
- return;
557
- }
558
- }
559
- /**
560
- * V8 optimization status bit meanings:
561
- * Bit 0 (1): is_function
562
- * Bit 4 (16): is_optimized (TurboFan)
563
- * Bit 5 (32): is_optimized (Maglev)
564
- * Bit 7 (128): is_baseline (Sparkplug)
565
- * Bit 3 (8): maybe_deoptimized
566
- */
567
- const statusNames = {
568
- 1: "interpreted",
569
- 129: "sparkplug",
570
- 17: "turbofan",
571
- 33: "maglev",
572
- 49: "turbofan+maglev",
573
- 32769: "optimized"
574
- };
575
- /** @return analysis of V8 optimization status per sample */
576
- function analyzeOptStatus(samples, statuses) {
577
- if (statuses.length === 0 || statuses[0] === void 0) return void 0;
578
- const byStatusCode = /* @__PURE__ */ new Map();
579
- let deoptCount = 0;
580
- for (let i = 0; i < samples.length; i++) {
581
- const status = statuses[i];
582
- if (status === void 0) continue;
583
- if (status & 8) deoptCount++;
584
- if (!byStatusCode.has(status)) byStatusCode.set(status, []);
585
- byStatusCode.get(status).push(samples[i]);
586
- }
587
- const byTier = {};
588
- for (const [status, times] of byStatusCode) {
589
- const name = statusNames[status] || `status=${status}`;
590
- const sorted = [...times].sort((a, b) => a - b);
591
- const median = sorted[Math.floor(sorted.length / 2)];
592
- byTier[name] = {
593
- count: times.length,
594
- medianMs: median
595
- };
596
- }
597
- return {
598
- byTier,
599
- deoptCount
600
- };
601
- }
602
-
603
- //#endregion
604
- //#region src/runners/CreateRunner.ts
605
- /** @return benchmark runner */
606
- async function createRunner(_runnerName) {
607
- return new BasicRunner();
608
- }
609
-
610
33
  //#endregion
611
34
  //#region src/runners/GcStats.ts
612
35
  /** Parse a single --trace-gc-nvp stderr line */
@@ -682,18 +105,6 @@ function aggregateGcStats(events) {
682
105
  };
683
106
  }
684
107
 
685
- //#endregion
686
- //#region src/runners/TimingUtils.ts
687
- const debugWorkerTiming = false;
688
- /** Get current time or 0 if debugging disabled */
689
- function getPerfNow() {
690
- return 0;
691
- }
692
- /** Calculate elapsed milliseconds between marks */
693
- function getElapsed(startMark, endMark) {
694
- return 0;
695
- }
696
-
697
108
  //#endregion
698
109
  //#region src/runners/RunnerOrchestrator.ts
699
110
  const logTiming = debugWorkerTiming ? (message) => console.log(`[RunnerOrchestrator] ${message}`) : () => {};
@@ -841,13 +252,8 @@ function createCleanup(worker, specName, reject) {
841
252
  }
842
253
  /** Create worker process with configuration */
843
254
  function createWorkerProcess(gcStats) {
844
- const workerPath = path.join(import.meta.dirname, "WorkerScript.ts");
845
- const execArgv = [
846
- "--expose-gc",
847
- "--allow-natives-syntax",
848
- "--experimental-strip-types",
849
- "--no-warnings=ExperimentalWarning"
850
- ];
255
+ const workerPath = resolveWorkerPath();
256
+ const execArgv = ["--expose-gc", "--allow-natives-syntax"];
851
257
  if (gcStats) execArgv.push("--trace-gc-nvp");
852
258
  return fork(workerPath, [], {
853
259
  execArgv,
@@ -858,6 +264,13 @@ function createWorkerProcess(gcStats) {
858
264
  }
859
265
  });
860
266
  }
267
+ /** Resolve WorkerScript path for dev (.ts) or dist (.mjs) */
268
+ function resolveWorkerPath() {
269
+ const dir = import.meta.dirname;
270
+ const tsPath = path.join(dir, "WorkerScript.ts");
271
+ if (existsSync(tsPath)) return tsPath;
272
+ return path.join(dir, "runners", "WorkerScript.mjs");
273
+ }
861
274
  /** @return handlers that attach GC stats and heap profile to results */
862
275
  function createWorkerHandlers(specName, startTime, gcEvents, resolve, reject) {
863
276
  return {
@@ -1430,14 +843,11 @@ function injectDiffColumns(reportGroups) {
1430
843
 
1431
844
  //#endregion
1432
845
  //#region src/cli/CliArgs.ts
1433
- const defaultTime = .642;
1434
846
  const defaultAdaptiveMaxTime = 20;
1435
- const defaultPauseInterval = 0;
1436
- const defaultPauseDuration = 100;
1437
847
  const cliOptions = {
1438
848
  time: {
1439
849
  type: "number",
1440
- default: defaultTime,
850
+ default: .642,
1441
851
  requiresArg: true,
1442
852
  describe: "test duration in seconds"
1443
853
  },
@@ -1532,12 +942,12 @@ const cliOptions = {
1532
942
  },
1533
943
  "pause-interval": {
1534
944
  type: "number",
1535
- default: defaultPauseInterval,
945
+ default: 0,
1536
946
  describe: "iterations between pauses for V8 optimization (0 to disable)"
1537
947
  },
1538
948
  "pause-duration": {
1539
949
  type: "number",
1540
- default: defaultPauseDuration,
950
+ default: 100,
1541
951
  describe: "pause duration in ms for V8 optimization"
1542
952
  },
1543
953
  batches: {
@@ -3672,5 +3082,5 @@ function getMostRecentModifiedDate(dir) {
3672
3082
  }
3673
3083
 
3674
3084
  //#endregion
3675
- export { timeSection as A, parseCliArgs as B, adaptiveSection as C, gcStatsSection as D, gcSection as E, generateHtmlReport as F, truncate as G, formatBytes as H, formatDateWithTimezone as I, average as J, isStatefulVariant as K, prepareHtmlData as L, formatConvergence as M, filterMatrix as N, optSection as O, parseMatrixFilter as P, exportPerfettoTrace as R, reportMatrixResults as S, cpuSection as T, integer as U, reportResults as V, timeMs as W, loadCasesModule as X, loadCaseData as Y, runDefaultMatrixBench as _, cliToMatrixOptions as a, gcStatsColumns as b, exportReports as c, matrixToReportGroups as d, parseBenchArgs as f, runDefaultBench as g, runBenchmarks as h, benchExports as i, totalTimeSection as j, runsSection as k, hasField as l, reportOptStatus as m, getBaselineVersion as n, defaultMatrixReport as o, printHeapReports as p, runMatrix as q, getCurrentGitVersion as r, defaultReport as s, formatGitVersion as t, matrixBenchExports as u, runMatrixSuite as v, buildGenericSections as w, heapTotalColumn as x, gcPauseColumn as y, defaultCliArgs as z };
3676
- //# sourceMappingURL=src-CGuaC3Wo.mjs.map
3085
+ export { timeSection as A, parseCliArgs as B, adaptiveSection as C, gcStatsSection as D, gcSection as E, generateHtmlReport as F, truncate as G, formatBytes as H, formatDateWithTimezone as I, loadCaseData as J, isStatefulVariant as K, prepareHtmlData as L, formatConvergence as M, filterMatrix as N, optSection as O, parseMatrixFilter as P, exportPerfettoTrace as R, reportMatrixResults as S, cpuSection as T, integer as U, reportResults as V, timeMs as W, loadCasesModule as Y, runDefaultMatrixBench as _, cliToMatrixOptions as a, gcStatsColumns as b, exportReports as c, matrixToReportGroups as d, parseBenchArgs as f, runDefaultBench as g, runBenchmarks as h, benchExports as i, totalTimeSection as j, runsSection as k, hasField as l, reportOptStatus as m, getBaselineVersion as n, defaultMatrixReport as o, printHeapReports as p, runMatrix as q, getCurrentGitVersion as r, defaultReport as s, formatGitVersion as t, matrixBenchExports as u, runMatrixSuite as v, buildGenericSections as w, heapTotalColumn as x, gcPauseColumn as y, defaultCliArgs as z };
3086
+ //# sourceMappingURL=src-D7zxOFGA.mjs.map