modestbench 0.0.1
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/CHANGELOG.md +45 -0
- package/LICENSE.md +55 -0
- package/README.md +699 -0
- package/dist/bootstrap.cjs +37 -0
- package/dist/bootstrap.cjs.map +1 -0
- package/dist/bootstrap.d.cts +17 -0
- package/dist/bootstrap.d.cts.map +1 -0
- package/dist/bootstrap.d.ts +17 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +33 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/cli/commands/history.cjs +459 -0
- package/dist/cli/commands/history.cjs.map +1 -0
- package/dist/cli/commands/history.d.cts +34 -0
- package/dist/cli/commands/history.d.cts.map +1 -0
- package/dist/cli/commands/history.d.ts +34 -0
- package/dist/cli/commands/history.d.ts.map +1 -0
- package/dist/cli/commands/history.js +422 -0
- package/dist/cli/commands/history.js.map +1 -0
- package/dist/cli/commands/init.cjs +566 -0
- package/dist/cli/commands/init.cjs.map +1 -0
- package/dist/cli/commands/init.d.cts +26 -0
- package/dist/cli/commands/init.d.cts.map +1 -0
- package/dist/cli/commands/init.d.ts +26 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +562 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/run.cjs +285 -0
- package/dist/cli/commands/run.cjs.map +1 -0
- package/dist/cli/commands/run.d.cts +37 -0
- package/dist/cli/commands/run.d.cts.map +1 -0
- package/dist/cli/commands/run.d.ts +37 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +248 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/index.cjs +523 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +58 -0
- package/dist/cli/index.d.cts.map +1 -0
- package/dist/cli/index.d.ts +58 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +515 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/manager.cjs +370 -0
- package/dist/config/manager.cjs.map +1 -0
- package/dist/config/manager.d.cts +46 -0
- package/dist/config/manager.d.cts.map +1 -0
- package/dist/config/manager.d.ts +46 -0
- package/dist/config/manager.d.ts.map +1 -0
- package/dist/config/manager.js +333 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/config/schema.cjs +182 -0
- package/dist/config/schema.cjs.map +1 -0
- package/dist/config/schema.d.cts +51 -0
- package/dist/config/schema.d.cts.map +1 -0
- package/dist/config/schema.d.ts +51 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +145 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/constants.cjs +22 -0
- package/dist/constants.cjs.map +1 -0
- package/dist/constants.d.cts +10 -0
- package/dist/constants.d.cts.map +1 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +19 -0
- package/dist/constants.js.map +1 -0
- package/dist/core/benchmark-schema.cjs +135 -0
- package/dist/core/benchmark-schema.cjs.map +1 -0
- package/dist/core/benchmark-schema.d.cts +139 -0
- package/dist/core/benchmark-schema.d.cts.map +1 -0
- package/dist/core/benchmark-schema.d.ts +139 -0
- package/dist/core/benchmark-schema.d.ts.map +1 -0
- package/dist/core/benchmark-schema.js +132 -0
- package/dist/core/benchmark-schema.js.map +1 -0
- package/dist/core/engine.cjs +669 -0
- package/dist/core/engine.cjs.map +1 -0
- package/dist/core/engine.d.cts +128 -0
- package/dist/core/engine.d.cts.map +1 -0
- package/dist/core/engine.d.ts +128 -0
- package/dist/core/engine.d.ts.map +1 -0
- package/dist/core/engine.js +632 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/engines/accurate-engine.cjs +292 -0
- package/dist/core/engines/accurate-engine.cjs.map +1 -0
- package/dist/core/engines/accurate-engine.d.cts +63 -0
- package/dist/core/engines/accurate-engine.d.cts.map +1 -0
- package/dist/core/engines/accurate-engine.d.ts +63 -0
- package/dist/core/engines/accurate-engine.d.ts.map +1 -0
- package/dist/core/engines/accurate-engine.js +288 -0
- package/dist/core/engines/accurate-engine.js.map +1 -0
- package/dist/core/engines/index.cjs +21 -0
- package/dist/core/engines/index.cjs.map +1 -0
- package/dist/core/engines/index.d.cts +16 -0
- package/dist/core/engines/index.d.cts.map +1 -0
- package/dist/core/engines/index.d.ts +16 -0
- package/dist/core/engines/index.d.ts.map +1 -0
- package/dist/core/engines/index.js +16 -0
- package/dist/core/engines/index.js.map +1 -0
- package/dist/core/engines/tinybench-engine.cjs +286 -0
- package/dist/core/engines/tinybench-engine.cjs.map +1 -0
- package/dist/core/engines/tinybench-engine.d.cts +18 -0
- package/dist/core/engines/tinybench-engine.d.cts.map +1 -0
- package/dist/core/engines/tinybench-engine.d.ts +18 -0
- package/dist/core/engines/tinybench-engine.d.ts.map +1 -0
- package/dist/core/engines/tinybench-engine.js +282 -0
- package/dist/core/engines/tinybench-engine.js.map +1 -0
- package/dist/core/error-manager.cjs +303 -0
- package/dist/core/error-manager.cjs.map +1 -0
- package/dist/core/error-manager.d.cts +77 -0
- package/dist/core/error-manager.d.cts.map +1 -0
- package/dist/core/error-manager.d.ts +77 -0
- package/dist/core/error-manager.d.ts.map +1 -0
- package/dist/core/error-manager.js +299 -0
- package/dist/core/error-manager.js.map +1 -0
- package/dist/core/loader.cjs +287 -0
- package/dist/core/loader.cjs.map +1 -0
- package/dist/core/loader.d.cts +55 -0
- package/dist/core/loader.d.cts.map +1 -0
- package/dist/core/loader.d.ts +55 -0
- package/dist/core/loader.d.ts.map +1 -0
- package/dist/core/loader.js +250 -0
- package/dist/core/loader.js.map +1 -0
- package/dist/core/stats-utils.cjs +99 -0
- package/dist/core/stats-utils.cjs.map +1 -0
- package/dist/core/stats-utils.d.cts +50 -0
- package/dist/core/stats-utils.d.cts.map +1 -0
- package/dist/core/stats-utils.d.ts +50 -0
- package/dist/core/stats-utils.d.ts.map +1 -0
- package/dist/core/stats-utils.js +94 -0
- package/dist/core/stats-utils.js.map +1 -0
- package/dist/index.cjs +64 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/progress/manager.cjs +325 -0
- package/dist/progress/manager.cjs.map +1 -0
- package/dist/progress/manager.d.cts +125 -0
- package/dist/progress/manager.d.cts.map +1 -0
- package/dist/progress/manager.d.ts +125 -0
- package/dist/progress/manager.d.ts.map +1 -0
- package/dist/progress/manager.js +321 -0
- package/dist/progress/manager.js.map +1 -0
- package/dist/reporters/csv.cjs +250 -0
- package/dist/reporters/csv.cjs.map +1 -0
- package/dist/reporters/csv.d.cts +92 -0
- package/dist/reporters/csv.d.cts.map +1 -0
- package/dist/reporters/csv.d.ts +92 -0
- package/dist/reporters/csv.d.ts.map +1 -0
- package/dist/reporters/csv.js +246 -0
- package/dist/reporters/csv.js.map +1 -0
- package/dist/reporters/human.cjs +516 -0
- package/dist/reporters/human.cjs.map +1 -0
- package/dist/reporters/human.d.cts +86 -0
- package/dist/reporters/human.d.cts.map +1 -0
- package/dist/reporters/human.d.ts +86 -0
- package/dist/reporters/human.d.ts.map +1 -0
- package/dist/reporters/human.js +509 -0
- package/dist/reporters/human.js.map +1 -0
- package/dist/reporters/index.cjs +17 -0
- package/dist/reporters/index.cjs.map +1 -0
- package/dist/reporters/index.d.cts +10 -0
- package/dist/reporters/index.d.cts.map +1 -0
- package/dist/reporters/index.d.ts +10 -0
- package/dist/reporters/index.d.ts.map +1 -0
- package/dist/reporters/index.js +10 -0
- package/dist/reporters/index.js.map +1 -0
- package/dist/reporters/json.cjs +215 -0
- package/dist/reporters/json.cjs.map +1 -0
- package/dist/reporters/json.d.cts +79 -0
- package/dist/reporters/json.d.cts.map +1 -0
- package/dist/reporters/json.d.ts +79 -0
- package/dist/reporters/json.d.ts.map +1 -0
- package/dist/reporters/json.js +211 -0
- package/dist/reporters/json.js.map +1 -0
- package/dist/reporters/registry.cjs +255 -0
- package/dist/reporters/registry.cjs.map +1 -0
- package/dist/reporters/registry.d.cts +155 -0
- package/dist/reporters/registry.d.cts.map +1 -0
- package/dist/reporters/registry.d.ts +155 -0
- package/dist/reporters/registry.d.ts.map +1 -0
- package/dist/reporters/registry.js +249 -0
- package/dist/reporters/registry.js.map +1 -0
- package/dist/reporters/simple.cjs +328 -0
- package/dist/reporters/simple.cjs.map +1 -0
- package/dist/reporters/simple.d.cts +51 -0
- package/dist/reporters/simple.d.cts.map +1 -0
- package/dist/reporters/simple.d.ts +51 -0
- package/dist/reporters/simple.d.ts.map +1 -0
- package/dist/reporters/simple.js +321 -0
- package/dist/reporters/simple.js.map +1 -0
- package/dist/schema/modestbench-config.schema.json +162 -0
- package/dist/storage/history.cjs +456 -0
- package/dist/storage/history.cjs.map +1 -0
- package/dist/storage/history.d.cts +99 -0
- package/dist/storage/history.d.cts.map +1 -0
- package/dist/storage/history.d.ts +99 -0
- package/dist/storage/history.d.ts.map +1 -0
- package/dist/storage/history.js +452 -0
- package/dist/storage/history.js.map +1 -0
- package/dist/types/cli.cjs +21 -0
- package/dist/types/cli.cjs.map +1 -0
- package/dist/types/cli.d.cts +296 -0
- package/dist/types/cli.d.cts.map +1 -0
- package/dist/types/cli.d.ts +296 -0
- package/dist/types/cli.d.ts.map +1 -0
- package/dist/types/cli.js +18 -0
- package/dist/types/cli.js.map +1 -0
- package/dist/types/core.cjs +14 -0
- package/dist/types/core.cjs.map +1 -0
- package/dist/types/core.d.cts +380 -0
- package/dist/types/core.d.cts.map +1 -0
- package/dist/types/core.d.ts +380 -0
- package/dist/types/core.d.ts.map +1 -0
- package/dist/types/core.js +13 -0
- package/dist/types/core.js.map +1 -0
- package/dist/types/index.cjs +27 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.d.cts +11 -0
- package/dist/types/index.d.cts.map +1 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +11 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/interfaces.cjs +10 -0
- package/dist/types/interfaces.cjs.map +1 -0
- package/dist/types/interfaces.d.cts +381 -0
- package/dist/types/interfaces.d.cts.map +1 -0
- package/dist/types/interfaces.d.ts +381 -0
- package/dist/types/interfaces.d.ts.map +1 -0
- package/dist/types/interfaces.js +9 -0
- package/dist/types/interfaces.js.map +1 -0
- package/dist/types/utility.cjs +92 -0
- package/dist/types/utility.cjs.map +1 -0
- package/dist/types/utility.d.cts +330 -0
- package/dist/types/utility.d.cts.map +1 -0
- package/dist/types/utility.d.ts +330 -0
- package/dist/types/utility.d.ts.map +1 -0
- package/dist/types/utility.js +78 -0
- package/dist/types/utility.js.map +1 -0
- package/package.json +211 -0
- package/src/bootstrap.ts +35 -0
- package/src/cli/commands/history.ts +569 -0
- package/src/cli/commands/init.ts +658 -0
- package/src/cli/commands/run.ts +346 -0
- package/src/cli/index.ts +642 -0
- package/src/config/manager.ts +387 -0
- package/src/config/schema.ts +188 -0
- package/src/constants.ts +21 -0
- package/src/core/benchmark-schema.ts +185 -0
- package/src/core/engine.ts +888 -0
- package/src/core/engines/accurate-engine.ts +408 -0
- package/src/core/engines/index.ts +16 -0
- package/src/core/engines/tinybench-engine.ts +335 -0
- package/src/core/error-manager.ts +372 -0
- package/src/core/loader.ts +324 -0
- package/src/core/stats-utils.ts +135 -0
- package/src/index.ts +46 -0
- package/src/progress/manager.ts +415 -0
- package/src/reporters/csv.ts +368 -0
- package/src/reporters/human.ts +707 -0
- package/src/reporters/index.ts +10 -0
- package/src/reporters/json.ts +302 -0
- package/src/reporters/registry.ts +349 -0
- package/src/reporters/simple.ts +459 -0
- package/src/storage/history.ts +600 -0
- package/src/types/cli.ts +312 -0
- package/src/types/core.ts +414 -0
- package/src/types/index.ts +18 -0
- package/src/types/interfaces.ts +451 -0
- package/src/types/utility.ts +446 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AccurateEngine - High-accuracy benchmark execution implementation
|
|
3
|
+
*
|
|
4
|
+
* Concrete implementation of ModestBenchEngine using measurement techniques
|
|
5
|
+
* adapted from bench-node for improved accuracy. Uses V8 optimization guards
|
|
6
|
+
* and array-based sample collection with IQR outlier removal.
|
|
7
|
+
*
|
|
8
|
+
* **Requirements:**
|
|
9
|
+
*
|
|
10
|
+
* - Node.js >= 20
|
|
11
|
+
* - --allow-natives-syntax flag (for V8 optimization guards)
|
|
12
|
+
*
|
|
13
|
+
* **Key Features:**
|
|
14
|
+
*
|
|
15
|
+
* - V8NeverOptimize guards prevent JIT optimization artifacts
|
|
16
|
+
* - Adaptive iteration calculation based on operation duration
|
|
17
|
+
* - IQR-based outlier removal for improved stability
|
|
18
|
+
* - Comprehensive statistics (mean, stdDev, variance, CV, percentiles)
|
|
19
|
+
* - Full ModestBench feature support (progress, abort, filtering)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type {
|
|
23
|
+
BenchmarkTask,
|
|
24
|
+
ModestBenchConfig,
|
|
25
|
+
Reporter,
|
|
26
|
+
TaskResult,
|
|
27
|
+
} from '../../types/index.js';
|
|
28
|
+
|
|
29
|
+
import { ModestBenchEngine } from '../engine.js';
|
|
30
|
+
import { calculateStatistics, removeOutliersIQR } from '../stats-utils.js';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* AccurateEngine - High-accuracy benchmarking with V8 optimization guards
|
|
34
|
+
*/
|
|
35
|
+
export class AccurateEngine extends ModestBenchEngine {
|
|
36
|
+
/**
|
|
37
|
+
* Maximum iterations per round to prevent overwhelming Node.js test runner
|
|
38
|
+
* and excessive memory usage
|
|
39
|
+
*/
|
|
40
|
+
private static readonly MAX_ITERATIONS_PER_ROUND = 10000;
|
|
41
|
+
|
|
42
|
+
private hasCheckedNativeSyntax = false;
|
|
43
|
+
|
|
44
|
+
private nativeSyntaxErrorShown = false;
|
|
45
|
+
|
|
46
|
+
private nativeSyntaxSupported = false;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Execute a single benchmark task using accurate measurement techniques
|
|
50
|
+
*
|
|
51
|
+
* This is the main integration point with ModestBench's engine abstraction.
|
|
52
|
+
*/
|
|
53
|
+
protected async executeBenchmarkTask(
|
|
54
|
+
taskName: string,
|
|
55
|
+
taskData: BenchmarkTask,
|
|
56
|
+
config: ModestBenchConfig,
|
|
57
|
+
_reporters: Reporter[] = [],
|
|
58
|
+
signal?: AbortSignal,
|
|
59
|
+
): Promise<TaskResult> {
|
|
60
|
+
try {
|
|
61
|
+
if (!taskData.fn || typeof taskData.fn !== 'function') {
|
|
62
|
+
throw new Error('Benchmark task must have a "fn" function property');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check for V8 native syntax support
|
|
66
|
+
const useOptGuards = this.checkNativeSyntax();
|
|
67
|
+
|
|
68
|
+
// Show helpful error if native syntax not available
|
|
69
|
+
if (!useOptGuards && !this.nativeSyntaxErrorShown) {
|
|
70
|
+
if (!config.quiet) {
|
|
71
|
+
console.warn(
|
|
72
|
+
'\n⚠️ AccurateEngine requires --allow-natives-syntax flag for best accuracy.',
|
|
73
|
+
'\nRunning in fallback mode (reduced accuracy).',
|
|
74
|
+
'\n\nTo enable V8 optimization guards:',
|
|
75
|
+
'\n node --allow-natives-syntax --test',
|
|
76
|
+
'\n or add to package.json: "test": "node --allow-natives-syntax ..."',
|
|
77
|
+
'\n',
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
this.nativeSyntaxErrorShown = true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Execute benchmark with or without opt guards
|
|
84
|
+
const rawSamples = useOptGuards
|
|
85
|
+
? await this.executeBenchmarkWithOptGuards(taskData.fn, config, signal)
|
|
86
|
+
: await this.executeBenchmarkBasic(taskData.fn, config, signal);
|
|
87
|
+
|
|
88
|
+
// Check if aborted
|
|
89
|
+
if (signal?.aborted) {
|
|
90
|
+
return {
|
|
91
|
+
cv: 0,
|
|
92
|
+
error: new Error('Benchmark aborted by user signal'),
|
|
93
|
+
iterations: rawSamples.length,
|
|
94
|
+
marginOfError: 0,
|
|
95
|
+
max: 0,
|
|
96
|
+
mean: 0,
|
|
97
|
+
metadata: taskData.metadata ?? {},
|
|
98
|
+
min: 0,
|
|
99
|
+
name: taskName,
|
|
100
|
+
opsPerSecond: 0,
|
|
101
|
+
p95: 0,
|
|
102
|
+
p99: 0,
|
|
103
|
+
stdDev: 0,
|
|
104
|
+
...(taskData.tags ? { tags: taskData.tags } : {}),
|
|
105
|
+
variance: 0,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Remove outliers using IQR method
|
|
110
|
+
const samples = removeOutliersIQR(rawSamples);
|
|
111
|
+
|
|
112
|
+
// Calculate statistics
|
|
113
|
+
const stats = calculateStatistics(samples);
|
|
114
|
+
|
|
115
|
+
// Transform to TaskResult
|
|
116
|
+
const taskResult: TaskResult = {
|
|
117
|
+
cv: stats.cv,
|
|
118
|
+
iterations: samples.length,
|
|
119
|
+
marginOfError: stats.marginOfError,
|
|
120
|
+
max: stats.max,
|
|
121
|
+
mean: stats.mean, // nanoseconds
|
|
122
|
+
metadata: taskData.metadata ?? {},
|
|
123
|
+
min: stats.min,
|
|
124
|
+
name: taskName,
|
|
125
|
+
opsPerSecond: 1e9 / stats.mean, // Convert ns to ops/sec
|
|
126
|
+
p95: stats.p95,
|
|
127
|
+
p99: stats.p99,
|
|
128
|
+
stdDev: stats.stdDev,
|
|
129
|
+
variance: stats.variance,
|
|
130
|
+
...(taskData.tags ? { tags: taskData.tags } : {}),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return taskResult;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
const executionError =
|
|
136
|
+
error instanceof Error ? error : new Error(String(error));
|
|
137
|
+
|
|
138
|
+
const errorResult: TaskResult = {
|
|
139
|
+
cv: 0,
|
|
140
|
+
error: executionError,
|
|
141
|
+
iterations: 0,
|
|
142
|
+
marginOfError: 0,
|
|
143
|
+
max: 0,
|
|
144
|
+
mean: 0,
|
|
145
|
+
metadata: taskData.metadata ?? {},
|
|
146
|
+
min: 0,
|
|
147
|
+
name: taskName,
|
|
148
|
+
opsPerSecond: 0,
|
|
149
|
+
p95: 0,
|
|
150
|
+
p99: 0,
|
|
151
|
+
stdDev: 0,
|
|
152
|
+
...(taskData.tags ? { tags: taskData.tags } : {}),
|
|
153
|
+
variance: 0,
|
|
154
|
+
};
|
|
155
|
+
return errorResult;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Calculate initial iterations based on benchmark characteristics Adapted
|
|
161
|
+
* from bench-node's getInitialIterations algorithm
|
|
162
|
+
*/
|
|
163
|
+
private async calculateInitialIterations(
|
|
164
|
+
fn: (...args: unknown[]) => unknown,
|
|
165
|
+
targetTime: number, // in seconds
|
|
166
|
+
): Promise<number> {
|
|
167
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
168
|
+
const timer = process.hrtime.bigint;
|
|
169
|
+
const MIN_RESOLUTION = 0.5; // nanoseconds
|
|
170
|
+
const SCALE = 1e9; // ns to seconds
|
|
171
|
+
|
|
172
|
+
// Run a quick test with 30 iterations
|
|
173
|
+
const testIterations = 30;
|
|
174
|
+
const start = timer();
|
|
175
|
+
|
|
176
|
+
for (let i = 0; i < testIterations; i++) {
|
|
177
|
+
fn();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const duration = Number(timer() - start);
|
|
181
|
+
const durationPerOp = Math.max(MIN_RESOLUTION, duration / testIterations);
|
|
182
|
+
|
|
183
|
+
// Calculate how many iterations we need for targetTime
|
|
184
|
+
const totalOpsForTargetTime = targetTime / (durationPerOp / SCALE);
|
|
185
|
+
|
|
186
|
+
return Math.min(
|
|
187
|
+
Number.MAX_SAFE_INTEGER,
|
|
188
|
+
Math.max(1, Math.round(totalOpsForTargetTime)),
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Check if V8 native syntax is available
|
|
194
|
+
*/
|
|
195
|
+
private checkNativeSyntax(): boolean {
|
|
196
|
+
if (this.hasCheckedNativeSyntax) {
|
|
197
|
+
return this.nativeSyntaxSupported;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
// Try to use a V8 intrinsic - this is the definitive test
|
|
202
|
+
// Must create AND execute the function to test if syntax is available
|
|
203
|
+
|
|
204
|
+
// SAFETY: This string is hardcoded and never influenced by user input.
|
|
205
|
+
// We use new Function() specifically to test V8 intrinsics support.
|
|
206
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval, @typescript-eslint/no-unsafe-call
|
|
207
|
+
new Function('%NeverOptimizeFunction(() => {})')();
|
|
208
|
+
this.nativeSyntaxSupported = true;
|
|
209
|
+
} catch {
|
|
210
|
+
this.nativeSyntaxSupported = false;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
this.hasCheckedNativeSyntax = true;
|
|
214
|
+
return this.nativeSyntaxSupported;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Execute benchmark WITHOUT V8 optimization guards (fallback)
|
|
219
|
+
*/
|
|
220
|
+
private async executeBenchmarkBasic(
|
|
221
|
+
fn: (...args: unknown[]) => unknown,
|
|
222
|
+
config: ModestBenchConfig,
|
|
223
|
+
signal?: AbortSignal,
|
|
224
|
+
): Promise<number[]> {
|
|
225
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
226
|
+
const timer = process.hrtime.bigint;
|
|
227
|
+
const samples: number[] = [];
|
|
228
|
+
const SCALE = 1e9;
|
|
229
|
+
|
|
230
|
+
const targetTime = config.time / 1000;
|
|
231
|
+
const initialIterations = await this.calculateInitialIterations(
|
|
232
|
+
fn,
|
|
233
|
+
targetTime,
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
if (config.warmup > 0) {
|
|
237
|
+
const warmupTime = Math.min(config.warmup / 1000, 0.05);
|
|
238
|
+
await this.runWarmup(fn, initialIterations, warmupTime);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const maxDuration = (config.time / 1000) * SCALE;
|
|
242
|
+
let timeSpent = 0;
|
|
243
|
+
let iterations = initialIterations;
|
|
244
|
+
|
|
245
|
+
while (timeSpent < maxDuration || samples.length < config.iterations) {
|
|
246
|
+
if (signal?.aborted) {
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const start = timer();
|
|
251
|
+
|
|
252
|
+
for (let i = 0; i < iterations; i++) {
|
|
253
|
+
fn();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const duration = Number(timer() - start);
|
|
257
|
+
const durationPerOp = duration / iterations;
|
|
258
|
+
|
|
259
|
+
samples.push(durationPerOp);
|
|
260
|
+
timeSpent += duration;
|
|
261
|
+
|
|
262
|
+
if (samples.length % 100 === 0) {
|
|
263
|
+
this.progressManager.forceUpdate();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const remainingTime = Math.max(0, (maxDuration - timeSpent) / SCALE);
|
|
267
|
+
iterations = Math.round(remainingTime / (durationPerOp / SCALE));
|
|
268
|
+
iterations = Math.max(
|
|
269
|
+
1,
|
|
270
|
+
Math.min(AccurateEngine.MAX_ITERATIONS_PER_ROUND, iterations),
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return samples;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Execute benchmark WITH V8 optimization guards (more accurate)
|
|
279
|
+
*/
|
|
280
|
+
private async executeBenchmarkWithOptGuards(
|
|
281
|
+
fn: (...args: unknown[]) => unknown,
|
|
282
|
+
config: ModestBenchConfig,
|
|
283
|
+
signal?: AbortSignal,
|
|
284
|
+
): Promise<number[]> {
|
|
285
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
286
|
+
const timer = process.hrtime.bigint;
|
|
287
|
+
const samples: number[] = [];
|
|
288
|
+
const SCALE = 1e9;
|
|
289
|
+
|
|
290
|
+
// Calculate iterations based on config
|
|
291
|
+
const targetTime = config.time / 1000; // ms to seconds
|
|
292
|
+
const initialIterations = await this.calculateInitialIterations(
|
|
293
|
+
fn,
|
|
294
|
+
targetTime,
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
// Run warmup
|
|
298
|
+
if (config.warmup > 0) {
|
|
299
|
+
const warmupTime = Math.min(config.warmup / 1000, 0.05); // Max 50ms warmup
|
|
300
|
+
await this.runWarmup(fn, initialIterations, warmupTime);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Create DoNotOptimize wrapper using V8 intrinsics
|
|
304
|
+
// This prevents V8 from optimizing away the benchmark code
|
|
305
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
306
|
+
const DoNotOptimize = new Function('x', 'return x');
|
|
307
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
308
|
+
const NeverOptimize = new Function(
|
|
309
|
+
'fn',
|
|
310
|
+
'%NeverOptimizeFunction(fn); return fn;',
|
|
311
|
+
);
|
|
312
|
+
// eslint-disable-next-line new-cap, @typescript-eslint/no-unsafe-call
|
|
313
|
+
const guardedDoNotOptimize = NeverOptimize(DoNotOptimize) as (
|
|
314
|
+
x: unknown,
|
|
315
|
+
) => unknown;
|
|
316
|
+
|
|
317
|
+
const maxDuration = (config.time / 1000) * SCALE;
|
|
318
|
+
let timeSpent = 0;
|
|
319
|
+
let iterations = initialIterations;
|
|
320
|
+
|
|
321
|
+
// Main benchmark loop
|
|
322
|
+
while (timeSpent < maxDuration || samples.length < config.iterations) {
|
|
323
|
+
if (signal?.aborted) {
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const start = timer();
|
|
328
|
+
|
|
329
|
+
for (let i = 0; i < iterations; i++) {
|
|
330
|
+
const result = fn();
|
|
331
|
+
guardedDoNotOptimize(result); // Prevent optimization
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const duration = Number(timer() - start);
|
|
335
|
+
const durationPerOp = duration / iterations;
|
|
336
|
+
|
|
337
|
+
samples.push(durationPerOp);
|
|
338
|
+
timeSpent += duration;
|
|
339
|
+
|
|
340
|
+
// Update progress every 100 samples
|
|
341
|
+
if (samples.length % 100 === 0) {
|
|
342
|
+
this.progressManager.forceUpdate();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Adjust iterations for next round
|
|
346
|
+
const remainingTime = Math.max(0, (maxDuration - timeSpent) / SCALE);
|
|
347
|
+
iterations = Math.round(remainingTime / (durationPerOp / SCALE));
|
|
348
|
+
iterations = Math.max(
|
|
349
|
+
1,
|
|
350
|
+
Math.min(AccurateEngine.MAX_ITERATIONS_PER_ROUND, iterations),
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return samples;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Run warmup phase Adapted from bench-node's runWarmup algorithm
|
|
359
|
+
*/
|
|
360
|
+
private async runWarmup(
|
|
361
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
362
|
+
fn: Function,
|
|
363
|
+
initialIterations: number,
|
|
364
|
+
warmupTime: number, // in seconds
|
|
365
|
+
): Promise<void> {
|
|
366
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
367
|
+
const timer = process.hrtime.bigint;
|
|
368
|
+
const MIN_RESOLUTION = 0.5;
|
|
369
|
+
const SCALE = 1e9;
|
|
370
|
+
const maxDuration = warmupTime * SCALE;
|
|
371
|
+
const minSamples = 10;
|
|
372
|
+
|
|
373
|
+
let timeSpent = 0n;
|
|
374
|
+
let samples = 0;
|
|
375
|
+
let iterations = Math.min(
|
|
376
|
+
initialIterations,
|
|
377
|
+
AccurateEngine.MAX_ITERATIONS_PER_ROUND,
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
while (Number(timeSpent) < maxDuration || samples <= minSamples) {
|
|
381
|
+
const start = timer();
|
|
382
|
+
|
|
383
|
+
for (let i = 0; i < iterations; i++) {
|
|
384
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
385
|
+
fn();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const duration = timer() - start;
|
|
389
|
+
timeSpent += duration;
|
|
390
|
+
samples++;
|
|
391
|
+
|
|
392
|
+
// Adjust iterations for next round
|
|
393
|
+
const durationPerOp = Math.max(
|
|
394
|
+
MIN_RESOLUTION,
|
|
395
|
+
Number(duration) / iterations,
|
|
396
|
+
);
|
|
397
|
+
const remainingTime = Math.max(
|
|
398
|
+
0,
|
|
399
|
+
(maxDuration - Number(timeSpent)) / SCALE,
|
|
400
|
+
);
|
|
401
|
+
iterations = Math.round(remainingTime / (durationPerOp / SCALE));
|
|
402
|
+
iterations = Math.max(
|
|
403
|
+
1,
|
|
404
|
+
Math.min(AccurateEngine.MAX_ITERATIONS_PER_ROUND, iterations),
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Benchmark Engine Implementations
|
|
3
|
+
*
|
|
4
|
+
* Concrete implementations of the ModestBenchEngine abstract class using
|
|
5
|
+
* different underlying benchmark libraries.
|
|
6
|
+
*
|
|
7
|
+
* Available engines:
|
|
8
|
+
*
|
|
9
|
+
* - TinybenchEngine: Default engine using tinybench library
|
|
10
|
+
* - AccurateEngine: High-accuracy engine with V8 optimization guards
|
|
11
|
+
*
|
|
12
|
+
* @packageDocumentation
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export { AccurateEngine } from './accurate-engine.js';
|
|
16
|
+
export { TinybenchEngine } from './tinybench-engine.js';
|