benchforge 0.1.11 → 0.2.4
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.
- package/LICENSE +20 -0
- package/README.md +99 -294
- package/bin/benchforge +1 -2
- package/dist/AnalyzeArchive-8NCJhmhS.mjs +145 -0
- package/dist/AnalyzeArchive-8NCJhmhS.mjs.map +1 -0
- package/dist/BenchMatrix-BZVrBB_h.mjs +1050 -0
- package/dist/BenchMatrix-BZVrBB_h.mjs.map +1 -0
- package/dist/{BenchRunner-BzyUfiyB.d.mts → BenchRunner-DglX1NOn.d.mts} +119 -66
- package/dist/CoverageSampler-D5T9DRqe.mjs +27 -0
- package/dist/CoverageSampler-D5T9DRqe.mjs.map +1 -0
- package/dist/Formatters-BWj3d4sv.mjs +95 -0
- package/dist/Formatters-BWj3d4sv.mjs.map +1 -0
- package/dist/{HeapSampler-B8dtKHn1.mjs → HeapSampler-Dq-hpXem.mjs} +4 -4
- package/dist/HeapSampler-Dq-hpXem.mjs.map +1 -0
- package/dist/RunBenchCLI-C17DrJz8.mjs +3075 -0
- package/dist/RunBenchCLI-C17DrJz8.mjs.map +1 -0
- package/dist/StatisticalUtils-BD92crgM.mjs +255 -0
- package/dist/StatisticalUtils-BD92crgM.mjs.map +1 -0
- package/dist/TimeSampler-Ds8n7l2B.mjs +29 -0
- package/dist/TimeSampler-Ds8n7l2B.mjs.map +1 -0
- package/dist/ViewerServer-BJhdnxlN.mjs +639 -0
- package/dist/ViewerServer-BJhdnxlN.mjs.map +1 -0
- package/dist/ViewerServer-CuMNdNBz.mjs +2 -0
- package/dist/bin/benchforge.mjs +4 -5
- package/dist/bin/benchforge.mjs.map +1 -1
- package/dist/index.d.mts +711 -558
- package/dist/index.mjs +98 -3
- package/dist/index.mjs.map +1 -0
- package/dist/runners/WorkerScript.d.mts +12 -4
- package/dist/runners/WorkerScript.mjs +77 -105
- package/dist/runners/WorkerScript.mjs.map +1 -1
- package/dist/viewer/assets/CIPlot-BkOvMoMa.js +1 -0
- package/dist/viewer/assets/HistogramKde-CmSyUFY0.js +1 -0
- package/dist/viewer/assets/LegendUtils-BJpbn_jr.js +55 -0
- package/dist/viewer/assets/SampleTimeSeries-C4VBhXr3.js +1 -0
- package/dist/viewer/assets/index-Br9bp_cX.js +153 -0
- package/dist/viewer/assets/index-NzXXe_CC.css +1 -0
- package/dist/viewer/index.html +19 -0
- package/dist/viewer/speedscope/LICENSE +21 -0
- package/dist/viewer/speedscope/SourceCodePro-Regular.ttf-ILST5JV6.woff2 +0 -0
- package/dist/viewer/speedscope/favicon-16x16-V2DMIAZS.js +2 -0
- package/dist/viewer/speedscope/favicon-16x16-V2DMIAZS.js.map +7 -0
- package/dist/viewer/speedscope/favicon-16x16-VSI62OPJ.png +0 -0
- package/dist/viewer/speedscope/favicon-32x32-3EB2YCUY.png +0 -0
- package/dist/viewer/speedscope/favicon-32x32-THY3JDJL.js +2 -0
- package/dist/viewer/speedscope/favicon-32x32-THY3JDJL.js.map +7 -0
- package/dist/viewer/speedscope/favicon-FOKUP5Y5.ico +0 -0
- package/dist/viewer/speedscope/favicon-M34RF7BI.js +2 -0
- package/dist/viewer/speedscope/favicon-M34RF7BI.js.map +7 -0
- package/dist/viewer/speedscope/file-format-schema.json +274 -0
- package/dist/viewer/speedscope/index.html +19 -0
- package/dist/viewer/speedscope/jfrview_bg-BLJXNNQB.wasm +0 -0
- package/dist/viewer/speedscope/perf-vertx-stacks-01-collapsed-all-ZNUIGAJL.txt +199 -0
- package/dist/viewer/speedscope/release.txt +3 -0
- package/dist/viewer/speedscope/source-code-pro.LICENSE.md +93 -0
- package/dist/viewer/speedscope/speedscope-GHPHNKXC.css +2 -0
- package/dist/viewer/speedscope/speedscope-GHPHNKXC.css.map +7 -0
- package/dist/viewer/speedscope/speedscope-QZFMJ7VP.js +212 -0
- package/dist/viewer/speedscope/speedscope-QZFMJ7VP.js.map +7 -0
- package/package.json +52 -27
- package/src/bin/benchforge.ts +2 -2
- package/src/cli/AnalyzeArchive.ts +232 -0
- package/src/cli/BrowserBench.ts +322 -0
- package/src/cli/CliArgs.ts +164 -51
- package/src/cli/CliExport.ts +179 -0
- package/src/cli/CliOptions.ts +147 -0
- package/src/cli/CliReport.ts +197 -0
- package/src/cli/FilterBenchmarks.ts +18 -30
- package/src/cli/RunBenchCLI.ts +132 -866
- package/src/cli/SuiteRunner.ts +160 -0
- package/src/cli/ViewerServer.ts +282 -0
- package/src/export/AllocExport.ts +121 -0
- package/src/export/ArchiveExport.ts +146 -0
- package/src/export/ArchiveFormat.ts +50 -0
- package/src/export/CoverageExport.ts +148 -0
- package/src/export/EditorUri.ts +10 -0
- package/src/export/PerfettoExport.ts +64 -99
- package/src/export/SpeedscopeTypes.ts +98 -0
- package/src/export/TimeExport.ts +115 -0
- package/src/index.ts +86 -67
- package/src/matrix/BenchMatrix.ts +230 -0
- package/src/matrix/CaseLoader.ts +8 -6
- package/src/matrix/MatrixDirRunner.ts +153 -0
- package/src/matrix/MatrixFilter.ts +49 -47
- package/src/matrix/MatrixInlineRunner.ts +50 -0
- package/src/matrix/MatrixReport.ts +90 -250
- package/src/matrix/VariantLoader.ts +5 -5
- package/src/profiling/browser/BenchLoop.ts +51 -0
- package/src/profiling/browser/BrowserCDP.ts +133 -0
- package/src/profiling/browser/BrowserGcStats.ts +33 -0
- package/src/profiling/browser/BrowserProfiler.ts +160 -0
- package/src/profiling/browser/CdpClient.ts +82 -0
- package/src/profiling/browser/CdpPage.ts +138 -0
- package/src/profiling/browser/ChromeLauncher.ts +158 -0
- package/src/profiling/browser/ChromeTraceEvent.ts +28 -0
- package/src/profiling/browser/PageLoadMode.ts +61 -0
- package/src/profiling/node/CoverageSampler.ts +27 -0
- package/src/profiling/node/CoverageTypes.ts +23 -0
- package/src/profiling/node/HeapSampleReport.ts +261 -0
- package/src/{heap-sample → profiling/node}/HeapSampler.ts +1 -2
- package/src/{heap-sample → profiling/node}/ResolvedProfile.ts +18 -9
- package/src/profiling/node/TimeSampler.ts +57 -0
- package/src/report/BenchmarkReport.ts +146 -0
- package/src/report/Colors.ts +9 -0
- package/src/report/Formatters.ts +110 -0
- package/src/report/GcSections.ts +151 -0
- package/src/{GitUtils.ts → report/GitUtils.ts} +18 -19
- package/src/report/HtmlReport.ts +223 -0
- package/src/report/ParseStats.ts +73 -0
- package/src/report/StandardSections.ts +147 -0
- package/src/report/ViewerSections.ts +286 -0
- package/src/report/text/TableReport.ts +253 -0
- package/src/report/text/TextReport.ts +123 -0
- package/src/runners/AdaptiveWrapper.ts +116 -236
- package/src/runners/BenchRunner.ts +20 -15
- package/src/{Benchmark.ts → runners/BenchmarkSpec.ts} +5 -6
- package/src/runners/CreateRunner.ts +5 -7
- package/src/runners/GcStats.ts +47 -50
- package/src/{MeasuredResults.ts → runners/MeasuredResults.ts} +43 -37
- package/src/runners/MergeBatches.ts +123 -0
- package/src/{NodeGC.ts → runners/NodeGC.ts} +2 -3
- package/src/runners/RunnerOrchestrator.ts +127 -243
- package/src/runners/RunnerUtils.ts +75 -1
- package/src/runners/SampleStats.ts +100 -0
- package/src/runners/TimingRunner.ts +244 -0
- package/src/runners/TimingUtils.ts +3 -2
- package/src/runners/WorkerScript.ts +135 -151
- package/src/stats/BootstrapDifference.ts +282 -0
- package/src/{PermutationTest.ts → stats/PermutationTest.ts} +8 -17
- package/src/stats/StatisticalUtils.ts +445 -0
- package/src/{tests → test}/AdaptiveConvergence.test.ts +10 -10
- package/src/test/AdaptiveRunner.test.ts +39 -41
- package/src/{tests → test}/AdaptiveSampling.test.ts +9 -9
- package/src/test/AdaptiveStatistics.integration.ts +2 -2
- package/src/{tests → test}/BenchMatrix.test.ts +19 -16
- package/src/test/BenchmarkReport.test.ts +63 -13
- package/src/test/BrowserBench.e2e.test.ts +186 -17
- package/src/test/BrowserBench.test.ts +10 -5
- package/src/test/BuildTimeSection.test.ts +130 -0
- package/src/test/CapSamples.test.ts +82 -0
- package/src/test/CoverageExport.test.ts +115 -0
- package/src/test/CoverageSampler.test.ts +33 -0
- package/src/test/HeapAttribution.test.ts +14 -14
- package/src/{tests → test}/MatrixFilter.test.ts +1 -1
- package/src/{tests → test}/MatrixReport.test.ts +1 -1
- package/src/test/PermutationTest.test.ts +1 -1
- package/src/{tests → test}/RealDataValidation.test.ts +6 -6
- package/src/test/RunBenchCLI.test.ts +39 -38
- package/src/test/RunnerOrchestrator.test.ts +12 -12
- package/src/test/StatisticalUtils.test.ts +48 -12
- package/src/{table-util/test → test}/TableReport.test.ts +2 -2
- package/src/test/TestUtils.ts +12 -7
- package/src/test/TimeExport.test.ts +139 -0
- package/src/test/TimeSampler.test.ts +37 -0
- package/src/test/ViewerLive.e2e.test.ts +159 -0
- package/src/test/ViewerStatic.static.e2e.test.ts +137 -0
- package/src/{tests → test}/fixtures/baseline/impl.ts +1 -1
- package/src/{tests → test}/fixtures/bevy30-samples.ts +3 -1
- package/src/test/fixtures/cases/asyncCases.ts +9 -0
- package/src/{tests → test}/fixtures/cases/cases.ts +5 -2
- package/src/test/fixtures/cases/variants/product.ts +2 -0
- package/src/test/fixtures/cases/variants/sum.ts +2 -0
- package/src/test/fixtures/discover/fast.ts +1 -0
- package/src/{tests → test}/fixtures/discover/slow.ts +1 -1
- package/src/test/fixtures/invalid/bad.ts +1 -0
- package/src/test/fixtures/loader/fast.ts +1 -0
- package/src/{tests → test}/fixtures/loader/slow.ts +1 -1
- package/src/test/fixtures/loader/stateful.ts +2 -0
- package/src/test/fixtures/stateful/stateful.ts +2 -0
- package/src/test/fixtures/variants/extra.ts +1 -0
- package/src/test/fixtures/variants/impl.ts +1 -0
- package/src/test/fixtures/worker/fast.ts +1 -0
- package/src/{tests → test}/fixtures/worker/slow.ts +1 -1
- package/src/viewer/DateFormat.ts +30 -0
- package/src/viewer/Helpers.ts +23 -0
- package/src/viewer/LineData.ts +120 -0
- package/src/viewer/Providers.ts +191 -0
- package/src/viewer/ReportData.ts +123 -0
- package/src/viewer/State.ts +49 -0
- package/src/viewer/Theme.ts +15 -0
- package/src/viewer/components/App.tsx +73 -0
- package/src/viewer/components/DropZone.tsx +71 -0
- package/src/viewer/components/LazyPlot.ts +33 -0
- package/src/viewer/components/SamplesPanel.tsx +214 -0
- package/src/viewer/components/Shell.tsx +26 -0
- package/src/viewer/components/SourcePanel.tsx +216 -0
- package/src/viewer/components/SummaryPanel.tsx +332 -0
- package/src/viewer/components/TabBar.tsx +131 -0
- package/src/viewer/components/TabContent.tsx +46 -0
- package/src/viewer/components/ThemeToggle.tsx +50 -0
- package/src/viewer/index.html +20 -0
- package/src/viewer/main.tsx +4 -0
- package/src/viewer/plots/CIPlot.ts +313 -0
- package/src/{html/browser → viewer/plots}/HistogramKde.ts +33 -38
- package/src/viewer/plots/LegendUtils.ts +134 -0
- package/src/viewer/plots/PlotTypes.ts +85 -0
- package/src/viewer/plots/RenderPlots.ts +230 -0
- package/src/viewer/plots/SampleTimeSeries.ts +306 -0
- package/src/viewer/plots/SvgHelpers.ts +136 -0
- package/src/viewer/plots/TimeSeriesMarks.ts +319 -0
- package/src/viewer/report.css +427 -0
- package/src/viewer/shell.css +357 -0
- package/src/viewer/tsconfig.json +11 -0
- package/dist/BrowserHeapSampler-B6asLKWQ.mjs +0 -202
- package/dist/BrowserHeapSampler-B6asLKWQ.mjs.map +0 -1
- package/dist/GcStats-wX7Xyblu.mjs +0 -77
- package/dist/GcStats-wX7Xyblu.mjs.map +0 -1
- package/dist/HeapSampler-B8dtKHn1.mjs.map +0 -1
- package/dist/TimingUtils-DwOwkc8G.mjs +0 -597
- package/dist/TimingUtils-DwOwkc8G.mjs.map +0 -1
- package/dist/browser/index.js +0 -914
- package/dist/src-B-DDaCa9.mjs +0 -3108
- package/dist/src-B-DDaCa9.mjs.map +0 -1
- package/src/BenchMatrix.ts +0 -380
- package/src/BenchmarkReport.ts +0 -161
- package/src/HtmlDataPrep.ts +0 -148
- package/src/StandardSections.ts +0 -261
- package/src/StatisticalUtils.ts +0 -175
- package/src/TypeUtil.ts +0 -8
- package/src/browser/BrowserGcStats.ts +0 -44
- package/src/browser/BrowserHeapSampler.ts +0 -271
- package/src/export/JsonExport.ts +0 -103
- package/src/export/JsonFormat.ts +0 -91
- package/src/export/SpeedscopeExport.ts +0 -202
- package/src/heap-sample/HeapSampleReport.ts +0 -269
- package/src/html/HtmlReport.ts +0 -131
- package/src/html/HtmlTemplate.ts +0 -284
- package/src/html/Types.ts +0 -88
- package/src/html/browser/CIPlot.ts +0 -287
- package/src/html/browser/LegendUtils.ts +0 -163
- package/src/html/browser/RenderPlots.ts +0 -263
- package/src/html/browser/SampleTimeSeries.ts +0 -389
- package/src/html/browser/Types.ts +0 -96
- package/src/html/browser/index.ts +0 -1
- package/src/html/index.ts +0 -17
- package/src/runners/BasicRunner.ts +0 -364
- package/src/table-util/ConvergenceFormatters.ts +0 -19
- package/src/table-util/Formatters.ts +0 -157
- package/src/table-util/README.md +0 -70
- package/src/table-util/TableReport.ts +0 -293
- package/src/tests/fixtures/cases/asyncCases.ts +0 -7
- package/src/tests/fixtures/cases/variants/product.ts +0 -2
- package/src/tests/fixtures/cases/variants/sum.ts +0 -2
- package/src/tests/fixtures/discover/fast.ts +0 -1
- package/src/tests/fixtures/invalid/bad.ts +0 -1
- package/src/tests/fixtures/loader/fast.ts +0 -1
- package/src/tests/fixtures/loader/stateful.ts +0 -2
- package/src/tests/fixtures/stateful/stateful.ts +0 -2
- package/src/tests/fixtures/variants/extra.ts +0 -1
- package/src/tests/fixtures/variants/impl.ts +0 -1
- package/src/tests/fixtures/worker/fast.ts +0 -1
- /package/src/{table-util/test → test}/TableValueExtractor.test.ts +0 -0
- /package/src/{table-util/test → test}/TableValueExtractor.ts +0 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/** Whether CI was computed from block-level or sample-level resampling */
|
|
2
|
+
export type CILevel = "block" | "sample";
|
|
3
|
+
|
|
4
|
+
/** Stat descriptor for multi-bootstrap: known stat kinds enable zero-alloc inner loops */
|
|
5
|
+
export type StatKind = "mean" | "min" | "max" | { percentile: number };
|
|
6
|
+
|
|
7
|
+
/** Bootstrap estimate with confidence interval and raw resample distribution */
|
|
8
|
+
export interface BootstrapResult {
|
|
9
|
+
/** Point estimate from the original sample */
|
|
10
|
+
estimate: number;
|
|
11
|
+
/** Confidence interval [lower, upper] from bootstrap resampling */
|
|
12
|
+
ci: [number, number];
|
|
13
|
+
/** Bootstrap resample distribution (for visualization) */
|
|
14
|
+
samples: number[];
|
|
15
|
+
/** Block-level (between-run) or sample-level (within-run) resampling */
|
|
16
|
+
ciLevel: CILevel;
|
|
17
|
+
/** Original sample count before subsampling (set only when cap applied) */
|
|
18
|
+
subsampled?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type CIDirection = "faster" | "slower" | "uncertain" | "equivalent";
|
|
22
|
+
|
|
23
|
+
/** Binned histogram for efficient transfer to browser */
|
|
24
|
+
export interface HistogramBin {
|
|
25
|
+
/** Bin center value */
|
|
26
|
+
x: number;
|
|
27
|
+
count: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Bootstrap confidence interval for percentage difference between two sample medians.
|
|
32
|
+
* Used for baseline comparisons: negative percent means current is faster.
|
|
33
|
+
*/
|
|
34
|
+
export interface DifferenceCI {
|
|
35
|
+
/** Observed percentage difference (current - baseline) / baseline */
|
|
36
|
+
percent: number;
|
|
37
|
+
/** Confidence interval [lower, upper] in percent */
|
|
38
|
+
ci: [number, number];
|
|
39
|
+
/** Whether the CI excludes zero: "faster", "slower", or "uncertain" */
|
|
40
|
+
direction: CIDirection;
|
|
41
|
+
/** Bootstrap distribution histogram for visualization */
|
|
42
|
+
histogram?: HistogramBin[];
|
|
43
|
+
/** Label for the CI plot title (e.g. "mean Δ%") */
|
|
44
|
+
label?: string;
|
|
45
|
+
/** Blocks trimmed per side [baseline, current] via Tukey fences */
|
|
46
|
+
trimmed?: [number, number];
|
|
47
|
+
/** Block-level (between-run) or sample-level (within-run) resampling */
|
|
48
|
+
ciLevel?: CILevel;
|
|
49
|
+
/** false when batch count is too low for reliable CI */
|
|
50
|
+
ciReliable?: boolean;
|
|
51
|
+
/** Original sample count before subsampling (set only when cap applied) */
|
|
52
|
+
subsampled?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Options for bootstrap resampling */
|
|
56
|
+
type BootstrapOptions = {
|
|
57
|
+
/** Number of bootstrap resamples (default: 10000) */
|
|
58
|
+
resamples?: number;
|
|
59
|
+
/** Confidence level 0-1 (default: 0.95) */
|
|
60
|
+
confidence?: number;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
interface StatOp {
|
|
64
|
+
origIndex: number;
|
|
65
|
+
compute: (buf: number[]) => number;
|
|
66
|
+
pointEstimate: (s: number[]) => number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const defaultConfidence = 0.95;
|
|
70
|
+
export const bootstrapSamples = 10000;
|
|
71
|
+
export const maxBootstrapInput = 10_000;
|
|
72
|
+
const outlierMultiplier = 1.5;
|
|
73
|
+
|
|
74
|
+
/** Swap direction labels for higher-is-better metrics (positive = faster) */
|
|
75
|
+
export function swapDirection(ci: DifferenceCI): DifferenceCI {
|
|
76
|
+
const swap: Record<CIDirection, CIDirection> = {
|
|
77
|
+
faster: "slower",
|
|
78
|
+
slower: "faster",
|
|
79
|
+
uncertain: "uncertain",
|
|
80
|
+
equivalent: "equivalent",
|
|
81
|
+
};
|
|
82
|
+
return { ...ci, direction: swap[ci.direction] };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Negate percent and CI for "higher is better" metrics (e.g., throughput) */
|
|
86
|
+
export function flipCI(ci: DifferenceCI): DifferenceCI {
|
|
87
|
+
return {
|
|
88
|
+
...ci,
|
|
89
|
+
percent: -ci.percent,
|
|
90
|
+
ci: [-ci.ci[1], -ci.ci[0]],
|
|
91
|
+
histogram: ci.histogram?.map(bin => ({ x: -bin.x, count: bin.count })),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Compute a statistic from samples by kind */
|
|
96
|
+
export function computeStat(samples: number[], kind: StatKind): number {
|
|
97
|
+
if (kind === "mean") return average(samples);
|
|
98
|
+
if (kind === "min") return minOf(samples);
|
|
99
|
+
if (kind === "max") return maxOf(samples);
|
|
100
|
+
return percentile(samples, kind.percentile);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** @return true if the stat kind supports bootstrap CI (min/max don't) */
|
|
104
|
+
export function isBootstrappable(kind: StatKind): boolean {
|
|
105
|
+
return kind !== "min" && kind !== "max";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** @return smallest value in samples (loop to avoid spread-arg limits) */
|
|
109
|
+
export function minOf(samples: number[]): number {
|
|
110
|
+
let min = samples[0];
|
|
111
|
+
for (let i = 1; i < samples.length; i++) {
|
|
112
|
+
if (samples[i] < min) min = samples[i];
|
|
113
|
+
}
|
|
114
|
+
return min;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** @return largest value in samples (loop to avoid spread-arg limits) */
|
|
118
|
+
export function maxOf(samples: number[]): number {
|
|
119
|
+
let max = samples[0];
|
|
120
|
+
for (let i = 1; i < samples.length; i++) {
|
|
121
|
+
if (samples[i] > max) max = samples[i];
|
|
122
|
+
}
|
|
123
|
+
return max;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** @return relative standard deviation (coefficient of variation) */
|
|
127
|
+
export function coefficientOfVariation(samples: number[]): number {
|
|
128
|
+
const mean = average(samples);
|
|
129
|
+
if (mean === 0) return 0;
|
|
130
|
+
const stdDev = standardDeviation(samples);
|
|
131
|
+
return stdDev / mean;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** @return median absolute deviation for robust variability measure */
|
|
135
|
+
export function medianAbsoluteDeviation(samples: number[]): number {
|
|
136
|
+
const med = median(samples);
|
|
137
|
+
const deviations = samples.map(x => Math.abs(x - med));
|
|
138
|
+
return median(deviations);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** @return outliers detected via Tukey's interquartile range method */
|
|
142
|
+
export function findOutliers(samples: number[]): {
|
|
143
|
+
rate: number;
|
|
144
|
+
indices: number[];
|
|
145
|
+
} {
|
|
146
|
+
const [lo, hi] = tukeyFences(samples, outlierMultiplier);
|
|
147
|
+
const indices = samples.flatMap((v, i) => (v < lo || v > hi ? [i] : []));
|
|
148
|
+
return { rate: indices.length / samples.length, indices };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Sample-level bootstrap CI: resample individual samples with replacement. */
|
|
152
|
+
export function sampleBootstrap(
|
|
153
|
+
samples: number[],
|
|
154
|
+
statFn: (s: number[]) => number,
|
|
155
|
+
options: BootstrapOptions = {},
|
|
156
|
+
): BootstrapResult {
|
|
157
|
+
const { resamples = bootstrapSamples, confidence: conf = defaultConfidence } =
|
|
158
|
+
options;
|
|
159
|
+
const sub = subsample(samples, maxBootstrapInput);
|
|
160
|
+
const buf = new Array(sub.length);
|
|
161
|
+
const stats = Array.from({ length: resamples }, () => {
|
|
162
|
+
resampleInto(sub, buf);
|
|
163
|
+
return statFn(buf);
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
estimate: statFn(samples),
|
|
167
|
+
ci: computeInterval(stats, conf),
|
|
168
|
+
samples: stats,
|
|
169
|
+
ciLevel: "sample",
|
|
170
|
+
...(sub !== samples && { subsampled: samples.length }),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Shared-resample bootstrap: one resample per iteration, all stats computed on it.
|
|
175
|
+
* Mean is computed first (non-destructive), then percentiles via in-place quickSelect. */
|
|
176
|
+
export function multiSampleBootstrap(
|
|
177
|
+
samples: number[],
|
|
178
|
+
stats: StatKind[],
|
|
179
|
+
options: BootstrapOptions = {},
|
|
180
|
+
): BootstrapResult[] {
|
|
181
|
+
const { resamples = bootstrapSamples, confidence: conf = defaultConfidence } =
|
|
182
|
+
options;
|
|
183
|
+
const sub = subsample(samples, maxBootstrapInput);
|
|
184
|
+
const n = sub.length;
|
|
185
|
+
const buf = new Array(n);
|
|
186
|
+
const ops = buildStatOps(stats, n);
|
|
187
|
+
const allStats = ops.map(() => new Array<number>(resamples));
|
|
188
|
+
|
|
189
|
+
for (let i = 0; i < resamples; i++) {
|
|
190
|
+
resampleInto(sub, buf);
|
|
191
|
+
for (let j = 0; j < ops.length; j++) {
|
|
192
|
+
allStats[j][i] = ops[j].compute(buf);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const capped = sub !== samples;
|
|
197
|
+
const results = new Array<BootstrapResult>(stats.length);
|
|
198
|
+
for (let j = 0; j < ops.length; j++) {
|
|
199
|
+
results[ops[j].origIndex] = {
|
|
200
|
+
estimate: ops[j].pointEstimate(samples),
|
|
201
|
+
ci: computeInterval(allStats[j], conf),
|
|
202
|
+
samples: allStats[j],
|
|
203
|
+
ciLevel: "sample",
|
|
204
|
+
...(capped && { subsampled: samples.length }),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return results;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Bootstrap CIs for multiple stats, dispatching block vs sample automatically.
|
|
211
|
+
* Returns undefined for non-bootstrappable stats (min/max). */
|
|
212
|
+
export function bootstrapCIs(
|
|
213
|
+
samples: number[],
|
|
214
|
+
batchOffsets: number[] | undefined,
|
|
215
|
+
stats: StatKind[],
|
|
216
|
+
options?: BootstrapOptions,
|
|
217
|
+
): (BootstrapResult | undefined)[] {
|
|
218
|
+
const bsStats = stats.filter(isBootstrappable);
|
|
219
|
+
if (bsStats.length === 0) return stats.map(() => undefined);
|
|
220
|
+
|
|
221
|
+
const hasBlocks = (batchOffsets?.length ?? 0) >= 2;
|
|
222
|
+
const bsResults = hasBlocks
|
|
223
|
+
? bsStats.map(s =>
|
|
224
|
+
blockBootstrap(samples, batchOffsets!, statKindToFn(s), options),
|
|
225
|
+
)
|
|
226
|
+
: multiSampleBootstrap(samples, bsStats, options);
|
|
227
|
+
|
|
228
|
+
const results: (BootstrapResult | undefined)[] = new Array(stats.length);
|
|
229
|
+
let bi = 0;
|
|
230
|
+
for (let i = 0; i < stats.length; i++) {
|
|
231
|
+
results[i] = isBootstrappable(stats[i]) ? bsResults[bi++] : undefined;
|
|
232
|
+
}
|
|
233
|
+
return results;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Convert StatKind to a stat function */
|
|
237
|
+
export function statKindToFn(kind: StatKind): (s: number[]) => number {
|
|
238
|
+
if (kind === "mean") return average;
|
|
239
|
+
if (kind === "min") return minOf;
|
|
240
|
+
if (kind === "max") return maxOf;
|
|
241
|
+
const p = kind.percentile;
|
|
242
|
+
return (s: number[]) => percentile(s, p);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Block bootstrap CI: Tukey-trim outlier batches, then resample per-block
|
|
246
|
+
* statFn values as independent observations. Requires 2+ blocks. */
|
|
247
|
+
export function blockBootstrap(
|
|
248
|
+
samples: number[],
|
|
249
|
+
blocks: number[],
|
|
250
|
+
statFn: (s: number[]) => number,
|
|
251
|
+
options: BootstrapOptions = {},
|
|
252
|
+
): BootstrapResult {
|
|
253
|
+
const { resamples = bootstrapSamples, confidence: conf = defaultConfidence } =
|
|
254
|
+
options;
|
|
255
|
+
const side = prepareBlocks(samples, blocks, statFn);
|
|
256
|
+
const stats = Array.from({ length: resamples }, () =>
|
|
257
|
+
average(createResample(side.blockVals)),
|
|
258
|
+
);
|
|
259
|
+
return {
|
|
260
|
+
estimate: statFn(side.filtered),
|
|
261
|
+
ci: computeInterval(stats, conf),
|
|
262
|
+
samples: stats,
|
|
263
|
+
ciLevel: "block",
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** @return mean of values */
|
|
268
|
+
export function average(values: number[]): number {
|
|
269
|
+
const sum = values.reduce((a, b) => a + b, 0);
|
|
270
|
+
return sum / values.length;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** @return median (50th percentile) of values */
|
|
274
|
+
export function median(values: number[]): number {
|
|
275
|
+
return percentile(values, 0.5);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** @return standard deviation with Bessel's correction */
|
|
279
|
+
export function standardDeviation(samples: number[]): number {
|
|
280
|
+
if (samples.length <= 1) return 0;
|
|
281
|
+
const mean = average(samples);
|
|
282
|
+
const variance =
|
|
283
|
+
samples.reduce((sum, x) => sum + (x - mean) ** 2, 0) / (samples.length - 1);
|
|
284
|
+
return Math.sqrt(variance);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** @return value at percentile p (0-1), using O(N) quickselect */
|
|
288
|
+
export function percentile(values: number[], p: number): number {
|
|
289
|
+
const copy = values.slice();
|
|
290
|
+
const k = Math.max(0, Math.ceil(copy.length * p) - 1);
|
|
291
|
+
return quickSelect(copy, k);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Hoare's selection: O(N) average k-th smallest element. Mutates arr. */
|
|
295
|
+
export function quickSelect(arr: number[], k: number): number {
|
|
296
|
+
let lo = 0;
|
|
297
|
+
let hi = arr.length - 1;
|
|
298
|
+
while (lo < hi) {
|
|
299
|
+
const [i, j] = partition(arr, lo, hi);
|
|
300
|
+
if (k <= j) hi = j;
|
|
301
|
+
else if (k >= i) lo = i;
|
|
302
|
+
else break;
|
|
303
|
+
}
|
|
304
|
+
return arr[k];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Fill buf in-place with bootstrap resample (with replacement) from source */
|
|
308
|
+
export function resampleInto(source: number[], buf: number[]): void {
|
|
309
|
+
const n = source.length;
|
|
310
|
+
for (let i = 0; i < n; i++) {
|
|
311
|
+
buf[i] = source[Math.floor(Math.random() * n)];
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** @return bootstrap resample with replacement */
|
|
316
|
+
export function createResample(samples: number[]): number[] {
|
|
317
|
+
const n = samples.length;
|
|
318
|
+
return Array.from(
|
|
319
|
+
{ length: n },
|
|
320
|
+
() => samples[Math.floor(Math.random() * n)],
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** @return Tukey fence bounds [lo, hi] for the given IQR multiplier.
|
|
325
|
+
* minIqr prevents degenerate fences when values are tightly clustered. */
|
|
326
|
+
export function tukeyFences(
|
|
327
|
+
values: number[],
|
|
328
|
+
multiplier = 3,
|
|
329
|
+
minIqr = 0,
|
|
330
|
+
): [lo: number, hi: number] {
|
|
331
|
+
const q1 = percentile(values, 0.25);
|
|
332
|
+
const q3 = percentile(values, 0.75);
|
|
333
|
+
const iqr = Math.max(q3 - q1, minIqr);
|
|
334
|
+
return [q1 - multiplier * iqr, q3 + multiplier * iqr];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** @return indices of values below the upper 3x IQR Tukey fence.
|
|
338
|
+
* Only trims slow outliers — fast batches reflect less environmental noise, not errors.
|
|
339
|
+
* Floors IQR at 2% of median to avoid over-trimming tightly clustered batch means. */
|
|
340
|
+
export function tukeyKeep(values: number[]): number[] {
|
|
341
|
+
if (values.length < 4) return values.map((_, i) => i);
|
|
342
|
+
const minIqr = median(values) * 0.02;
|
|
343
|
+
const [, hi] = tukeyFences(values, 3, minIqr);
|
|
344
|
+
return values.flatMap((v, i) => (v <= hi ? [i] : []));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** @return samples split into blocks by offset boundaries */
|
|
348
|
+
export function splitByOffsets(
|
|
349
|
+
samples: number[],
|
|
350
|
+
offsets: number[],
|
|
351
|
+
): number[][] {
|
|
352
|
+
return offsets.map((start, i) => {
|
|
353
|
+
const end = i + 1 < offsets.length ? offsets[i + 1] : samples.length;
|
|
354
|
+
return samples.slice(start, end);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** @return per-block statistic values from sample data split by offsets */
|
|
359
|
+
export function blockValues(
|
|
360
|
+
samples: number[],
|
|
361
|
+
offsets: number[],
|
|
362
|
+
fn: (s: number[]) => number,
|
|
363
|
+
): number[] {
|
|
364
|
+
return splitByOffsets(samples, offsets).map(fn);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** Tukey-trim outlier blocks and compute per-block statistic for one side */
|
|
368
|
+
export function prepareBlocks(
|
|
369
|
+
samples: number[],
|
|
370
|
+
offsets: number[],
|
|
371
|
+
fn: (s: number[]) => number,
|
|
372
|
+
noTrim?: boolean,
|
|
373
|
+
): { blockVals: number[]; filtered: number[]; trimCount: number } {
|
|
374
|
+
const splits = splitByOffsets(samples, offsets);
|
|
375
|
+
const means = splits.map(average);
|
|
376
|
+
const keep = noTrim ? means.map((_, i) => i) : tukeyKeep(means);
|
|
377
|
+
return {
|
|
378
|
+
blockVals: keep.map(i => fn(splits[i])),
|
|
379
|
+
filtered: keep.flatMap(i => splits[i]),
|
|
380
|
+
trimCount: means.length - keep.length,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** Random subsample without replacement via partial Fisher-Yates. Returns original if n <= max. */
|
|
385
|
+
export function subsample(samples: number[], max: number): number[] {
|
|
386
|
+
if (samples.length <= max) return samples;
|
|
387
|
+
const copy = samples.slice();
|
|
388
|
+
for (let i = 0; i < max; i++) {
|
|
389
|
+
const j = i + Math.floor(Math.random() * (copy.length - i));
|
|
390
|
+
[copy[i], copy[j]] = [copy[j], copy[i]];
|
|
391
|
+
}
|
|
392
|
+
return copy.slice(0, max);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** @return confidence interval [lower, upper] */
|
|
396
|
+
export function computeInterval(
|
|
397
|
+
values: number[],
|
|
398
|
+
conf: number,
|
|
399
|
+
): [number, number] {
|
|
400
|
+
const alpha = (1 - conf) / 2;
|
|
401
|
+
return [percentile(values, alpha), percentile(values, 1 - alpha)];
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** Build stat operations in safe order: mean/min/max first (non-destructive),
|
|
405
|
+
* then percentiles ascending (use quickSelect which mutates buf) */
|
|
406
|
+
function buildStatOps(stats: StatKind[], n: number): StatOp[] {
|
|
407
|
+
const simple = (order: number, i: number, fn: (s: number[]) => number) => ({
|
|
408
|
+
order,
|
|
409
|
+
compute: fn,
|
|
410
|
+
pointEstimate: fn,
|
|
411
|
+
origIndex: i,
|
|
412
|
+
});
|
|
413
|
+
const ops = stats.map((s, i): StatOp & { order: number } => {
|
|
414
|
+
if (s === "mean") return simple(-3, i, average);
|
|
415
|
+
if (s === "min") return simple(-2, i, minOf);
|
|
416
|
+
if (s === "max") return simple(-1, i, maxOf);
|
|
417
|
+
const p = s.percentile;
|
|
418
|
+
const k = Math.max(0, Math.ceil(n * p) - 1);
|
|
419
|
+
return {
|
|
420
|
+
order: p,
|
|
421
|
+
origIndex: i,
|
|
422
|
+
compute: (buf: number[]) => quickSelect(buf, k),
|
|
423
|
+
pointEstimate: (v: number[]) => percentile(v, p),
|
|
424
|
+
};
|
|
425
|
+
});
|
|
426
|
+
ops.sort((a, b) => a.order - b.order);
|
|
427
|
+
return ops;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** Hoare partition around the midpoint pivot. @return [i, j] boundary indices. */
|
|
431
|
+
function partition(arr: number[], lo: number, hi: number): [number, number] {
|
|
432
|
+
const pivot = arr[lo + ((hi - lo) >> 1)];
|
|
433
|
+
let i = lo;
|
|
434
|
+
let j = hi;
|
|
435
|
+
while (i <= j) {
|
|
436
|
+
while (arr[i] < pivot) i++;
|
|
437
|
+
while (arr[j] > pivot) j--;
|
|
438
|
+
if (i <= j) {
|
|
439
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
440
|
+
i++;
|
|
441
|
+
j--;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return [i, j];
|
|
445
|
+
}
|
|
@@ -2,7 +2,7 @@ import { test } from "vitest";
|
|
|
2
2
|
import { checkConvergence } from "../runners/AdaptiveWrapper.ts";
|
|
3
3
|
import { bevy30SamplesNs } from "./fixtures/bevy30-samples.ts";
|
|
4
4
|
|
|
5
|
-
test("convergence with insufficient samples", () => {
|
|
5
|
+
test.skip("convergence with insufficient samples", () => {
|
|
6
6
|
const samples = [1e6, 2e6, 3e6]; // 3 samples in nanoseconds
|
|
7
7
|
const result = checkConvergence(samples);
|
|
8
8
|
|
|
@@ -14,7 +14,7 @@ test("convergence with insufficient samples", () => {
|
|
|
14
14
|
}
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
test("convergence with stable samples", () => {
|
|
17
|
+
test.skip("convergence with stable samples", () => {
|
|
18
18
|
// Create very stable samples (all within 1% of each other)
|
|
19
19
|
const base = 50e6; // 50ms in nanoseconds
|
|
20
20
|
const samples = Array.from(
|
|
@@ -30,7 +30,7 @@ test("convergence with stable samples", () => {
|
|
|
30
30
|
}
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
test("convergence with drifting median", () => {
|
|
33
|
+
test.skip("convergence with drifting median", () => {
|
|
34
34
|
// Create samples with increasing median over time
|
|
35
35
|
const samples = Array.from(
|
|
36
36
|
{ length: 200 },
|
|
@@ -48,7 +48,7 @@ test("convergence with drifting median", () => {
|
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
test("convergence with outliers", () => {
|
|
51
|
+
test.skip("convergence with outliers", () => {
|
|
52
52
|
// Create stable samples with occasional outliers every 20 samples
|
|
53
53
|
const base = 50e6;
|
|
54
54
|
const samples = Array.from({ length: 200 }, (_, i) =>
|
|
@@ -63,7 +63,7 @@ test("convergence with outliers", () => {
|
|
|
63
63
|
}
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
test("convergence with real bevy30 data - early samples", () => {
|
|
66
|
+
test.skip("convergence with real bevy30 data - early samples", () => {
|
|
67
67
|
// Test with first 100 samples (should show initial instability)
|
|
68
68
|
const early = bevy30SamplesNs.slice(0, 100);
|
|
69
69
|
const result = checkConvergence(early);
|
|
@@ -78,7 +78,7 @@ test("convergence with real bevy30 data - early samples", () => {
|
|
|
78
78
|
);
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
test("convergence with real bevy30 data - middle samples", () => {
|
|
81
|
+
test.skip("convergence with real bevy30 data - middle samples", () => {
|
|
82
82
|
// Test with middle 200 samples (should be more stable)
|
|
83
83
|
const middle = bevy30SamplesNs.slice(200, 400);
|
|
84
84
|
const result = checkConvergence(middle);
|
|
@@ -92,7 +92,7 @@ test("convergence with real bevy30 data - middle samples", () => {
|
|
|
92
92
|
);
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
-
test("convergence with real bevy30 data - all samples", () => {
|
|
95
|
+
test.skip("convergence with real bevy30 data - all samples", () => {
|
|
96
96
|
const result = checkConvergence(bevy30SamplesNs);
|
|
97
97
|
|
|
98
98
|
if (result.confidence > 100 || result.confidence < 0) {
|
|
@@ -109,7 +109,7 @@ test("convergence with real bevy30 data - all samples", () => {
|
|
|
109
109
|
);
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
-
test("convergence progression over time", () => {
|
|
112
|
+
test.skip("convergence progression over time", () => {
|
|
113
113
|
const checkpoints = [50, 100, 150, 200, 300, 400, 500, 610];
|
|
114
114
|
const progressions = checkpoints.map(n => {
|
|
115
115
|
const result = checkConvergence(bevy30SamplesNs.slice(0, n));
|
|
@@ -132,7 +132,7 @@ test("convergence progression over time", () => {
|
|
|
132
132
|
}
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
-
test("window size adaptation for different execution times", () => {
|
|
135
|
+
test.skip("window size adaptation for different execution times", () => {
|
|
136
136
|
// Fast samples (microseconds)
|
|
137
137
|
const fastSamples = Array.from(
|
|
138
138
|
{ length: 100 },
|
|
@@ -155,7 +155,7 @@ test("window size adaptation for different execution times", () => {
|
|
|
155
155
|
}
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
-
test("outlier impact calculation", () => {
|
|
158
|
+
test.skip("outlier impact calculation", () => {
|
|
159
159
|
// 95 stable samples + 5 outliers (2x slower)
|
|
160
160
|
const base = 50e6; // 50ms
|
|
161
161
|
const stable = Array.from(
|
|
@@ -1,42 +1,40 @@
|
|
|
1
1
|
import { expect, test } from "vitest";
|
|
2
|
-
import type { BenchmarkSpec } from "../Benchmark.ts";
|
|
3
2
|
import {
|
|
4
3
|
checkConvergence,
|
|
5
4
|
createAdaptiveWrapper,
|
|
6
5
|
} from "../runners/AdaptiveWrapper.ts";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const runner = new BasicRunner();
|
|
6
|
+
import type { BenchmarkSpec } from "../runners/BenchmarkSpec.ts";
|
|
7
|
+
import { TimingRunner } from "../runners/TimingRunner.ts";
|
|
8
|
+
|
|
9
|
+
test.skip("adaptive runner collects samples for minimum time", {
|
|
10
|
+
timeout: 10000,
|
|
11
|
+
}, async () => {
|
|
12
|
+
const runner = new TimingRunner();
|
|
13
|
+
const adaptive = createAdaptiveWrapper(runner, {
|
|
14
|
+
minTime: 100,
|
|
15
|
+
maxTime: 300,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const benchmark: BenchmarkSpec = {
|
|
19
|
+
name: "test-min-time",
|
|
20
|
+
fn: () => {
|
|
21
|
+
let sum = 0;
|
|
22
|
+
for (let i = 0; i < 1000; i++) sum += i;
|
|
23
|
+
return sum;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const start = performance.now();
|
|
28
|
+
const results = await adaptive.runBench(benchmark, { minTime: 100 });
|
|
29
|
+
const elapsed = performance.now() - start;
|
|
30
|
+
|
|
31
|
+
expect(results).toHaveLength(1);
|
|
32
|
+
expect(results[0].samples.length).toBeGreaterThan(0);
|
|
33
|
+
expect(elapsed).toBeGreaterThanOrEqual(100);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test.skip("adaptive runner respects max time limit", async () => {
|
|
37
|
+
const runner = new TimingRunner();
|
|
40
38
|
const adaptive = createAdaptiveWrapper(runner, {
|
|
41
39
|
minTime: 100,
|
|
42
40
|
maxTime: 2000,
|
|
@@ -62,8 +60,8 @@ test("adaptive runner respects max time limit", async () => {
|
|
|
62
60
|
expect(results[0].totalTime).toBeLessThanOrEqual(2.0);
|
|
63
61
|
});
|
|
64
62
|
|
|
65
|
-
test("adaptive runner merges results correctly", async () => {
|
|
66
|
-
const runner = new
|
|
63
|
+
test.skip("adaptive runner merges results correctly", async () => {
|
|
64
|
+
const runner = new TimingRunner();
|
|
67
65
|
const adaptive = createAdaptiveWrapper(runner, {
|
|
68
66
|
minTime: 100,
|
|
69
67
|
maxTime: 200,
|
|
@@ -101,8 +99,8 @@ test("adaptive runner merges results correctly", async () => {
|
|
|
101
99
|
expect(result.totalTime).toBeGreaterThan(0);
|
|
102
100
|
}, 10000);
|
|
103
101
|
|
|
104
|
-
test("convergence detection with stable benchmark", async () => {
|
|
105
|
-
const runner = new
|
|
102
|
+
test.skip("convergence detection with stable benchmark", async () => {
|
|
103
|
+
const runner = new TimingRunner();
|
|
106
104
|
const adaptive = createAdaptiveWrapper(runner, {
|
|
107
105
|
minTime: 100,
|
|
108
106
|
maxTime: 2000,
|
|
@@ -129,8 +127,8 @@ test("convergence detection with stable benchmark", async () => {
|
|
|
129
127
|
expect(result.convergence?.reason).toBeDefined();
|
|
130
128
|
});
|
|
131
129
|
|
|
132
|
-
test("convergence detection with variable benchmark", async () => {
|
|
133
|
-
const runner = new
|
|
130
|
+
test.skip("convergence detection with variable benchmark", async () => {
|
|
131
|
+
const runner = new TimingRunner();
|
|
134
132
|
const adaptive = createAdaptiveWrapper(runner, {
|
|
135
133
|
minTime: 100,
|
|
136
134
|
maxTime: 1000,
|
|
@@ -162,7 +160,7 @@ test("convergence detection with variable benchmark", async () => {
|
|
|
162
160
|
expect(result.convergence?.confidence).toBeLessThanOrEqual(100);
|
|
163
161
|
});
|
|
164
162
|
|
|
165
|
-
test("checkConvergence function basics", () => {
|
|
163
|
+
test.skip("checkConvergence function basics", () => {
|
|
166
164
|
// Not enough samples
|
|
167
165
|
const fewSamples = [1e6, 1.1e6, 1e6];
|
|
168
166
|
const fewResult = checkConvergence(fewSamples);
|