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.
Files changed (275) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE.md +55 -0
  3. package/README.md +699 -0
  4. package/dist/bootstrap.cjs +37 -0
  5. package/dist/bootstrap.cjs.map +1 -0
  6. package/dist/bootstrap.d.cts +17 -0
  7. package/dist/bootstrap.d.cts.map +1 -0
  8. package/dist/bootstrap.d.ts +17 -0
  9. package/dist/bootstrap.d.ts.map +1 -0
  10. package/dist/bootstrap.js +33 -0
  11. package/dist/bootstrap.js.map +1 -0
  12. package/dist/cli/commands/history.cjs +459 -0
  13. package/dist/cli/commands/history.cjs.map +1 -0
  14. package/dist/cli/commands/history.d.cts +34 -0
  15. package/dist/cli/commands/history.d.cts.map +1 -0
  16. package/dist/cli/commands/history.d.ts +34 -0
  17. package/dist/cli/commands/history.d.ts.map +1 -0
  18. package/dist/cli/commands/history.js +422 -0
  19. package/dist/cli/commands/history.js.map +1 -0
  20. package/dist/cli/commands/init.cjs +566 -0
  21. package/dist/cli/commands/init.cjs.map +1 -0
  22. package/dist/cli/commands/init.d.cts +26 -0
  23. package/dist/cli/commands/init.d.cts.map +1 -0
  24. package/dist/cli/commands/init.d.ts +26 -0
  25. package/dist/cli/commands/init.d.ts.map +1 -0
  26. package/dist/cli/commands/init.js +562 -0
  27. package/dist/cli/commands/init.js.map +1 -0
  28. package/dist/cli/commands/run.cjs +285 -0
  29. package/dist/cli/commands/run.cjs.map +1 -0
  30. package/dist/cli/commands/run.d.cts +37 -0
  31. package/dist/cli/commands/run.d.cts.map +1 -0
  32. package/dist/cli/commands/run.d.ts +37 -0
  33. package/dist/cli/commands/run.d.ts.map +1 -0
  34. package/dist/cli/commands/run.js +248 -0
  35. package/dist/cli/commands/run.js.map +1 -0
  36. package/dist/cli/index.cjs +523 -0
  37. package/dist/cli/index.cjs.map +1 -0
  38. package/dist/cli/index.d.cts +58 -0
  39. package/dist/cli/index.d.cts.map +1 -0
  40. package/dist/cli/index.d.ts +58 -0
  41. package/dist/cli/index.d.ts.map +1 -0
  42. package/dist/cli/index.js +515 -0
  43. package/dist/cli/index.js.map +1 -0
  44. package/dist/config/manager.cjs +370 -0
  45. package/dist/config/manager.cjs.map +1 -0
  46. package/dist/config/manager.d.cts +46 -0
  47. package/dist/config/manager.d.cts.map +1 -0
  48. package/dist/config/manager.d.ts +46 -0
  49. package/dist/config/manager.d.ts.map +1 -0
  50. package/dist/config/manager.js +333 -0
  51. package/dist/config/manager.js.map +1 -0
  52. package/dist/config/schema.cjs +182 -0
  53. package/dist/config/schema.cjs.map +1 -0
  54. package/dist/config/schema.d.cts +51 -0
  55. package/dist/config/schema.d.cts.map +1 -0
  56. package/dist/config/schema.d.ts +51 -0
  57. package/dist/config/schema.d.ts.map +1 -0
  58. package/dist/config/schema.js +145 -0
  59. package/dist/config/schema.js.map +1 -0
  60. package/dist/constants.cjs +22 -0
  61. package/dist/constants.cjs.map +1 -0
  62. package/dist/constants.d.cts +10 -0
  63. package/dist/constants.d.cts.map +1 -0
  64. package/dist/constants.d.ts +10 -0
  65. package/dist/constants.d.ts.map +1 -0
  66. package/dist/constants.js +19 -0
  67. package/dist/constants.js.map +1 -0
  68. package/dist/core/benchmark-schema.cjs +135 -0
  69. package/dist/core/benchmark-schema.cjs.map +1 -0
  70. package/dist/core/benchmark-schema.d.cts +139 -0
  71. package/dist/core/benchmark-schema.d.cts.map +1 -0
  72. package/dist/core/benchmark-schema.d.ts +139 -0
  73. package/dist/core/benchmark-schema.d.ts.map +1 -0
  74. package/dist/core/benchmark-schema.js +132 -0
  75. package/dist/core/benchmark-schema.js.map +1 -0
  76. package/dist/core/engine.cjs +669 -0
  77. package/dist/core/engine.cjs.map +1 -0
  78. package/dist/core/engine.d.cts +128 -0
  79. package/dist/core/engine.d.cts.map +1 -0
  80. package/dist/core/engine.d.ts +128 -0
  81. package/dist/core/engine.d.ts.map +1 -0
  82. package/dist/core/engine.js +632 -0
  83. package/dist/core/engine.js.map +1 -0
  84. package/dist/core/engines/accurate-engine.cjs +292 -0
  85. package/dist/core/engines/accurate-engine.cjs.map +1 -0
  86. package/dist/core/engines/accurate-engine.d.cts +63 -0
  87. package/dist/core/engines/accurate-engine.d.cts.map +1 -0
  88. package/dist/core/engines/accurate-engine.d.ts +63 -0
  89. package/dist/core/engines/accurate-engine.d.ts.map +1 -0
  90. package/dist/core/engines/accurate-engine.js +288 -0
  91. package/dist/core/engines/accurate-engine.js.map +1 -0
  92. package/dist/core/engines/index.cjs +21 -0
  93. package/dist/core/engines/index.cjs.map +1 -0
  94. package/dist/core/engines/index.d.cts +16 -0
  95. package/dist/core/engines/index.d.cts.map +1 -0
  96. package/dist/core/engines/index.d.ts +16 -0
  97. package/dist/core/engines/index.d.ts.map +1 -0
  98. package/dist/core/engines/index.js +16 -0
  99. package/dist/core/engines/index.js.map +1 -0
  100. package/dist/core/engines/tinybench-engine.cjs +286 -0
  101. package/dist/core/engines/tinybench-engine.cjs.map +1 -0
  102. package/dist/core/engines/tinybench-engine.d.cts +18 -0
  103. package/dist/core/engines/tinybench-engine.d.cts.map +1 -0
  104. package/dist/core/engines/tinybench-engine.d.ts +18 -0
  105. package/dist/core/engines/tinybench-engine.d.ts.map +1 -0
  106. package/dist/core/engines/tinybench-engine.js +282 -0
  107. package/dist/core/engines/tinybench-engine.js.map +1 -0
  108. package/dist/core/error-manager.cjs +303 -0
  109. package/dist/core/error-manager.cjs.map +1 -0
  110. package/dist/core/error-manager.d.cts +77 -0
  111. package/dist/core/error-manager.d.cts.map +1 -0
  112. package/dist/core/error-manager.d.ts +77 -0
  113. package/dist/core/error-manager.d.ts.map +1 -0
  114. package/dist/core/error-manager.js +299 -0
  115. package/dist/core/error-manager.js.map +1 -0
  116. package/dist/core/loader.cjs +287 -0
  117. package/dist/core/loader.cjs.map +1 -0
  118. package/dist/core/loader.d.cts +55 -0
  119. package/dist/core/loader.d.cts.map +1 -0
  120. package/dist/core/loader.d.ts +55 -0
  121. package/dist/core/loader.d.ts.map +1 -0
  122. package/dist/core/loader.js +250 -0
  123. package/dist/core/loader.js.map +1 -0
  124. package/dist/core/stats-utils.cjs +99 -0
  125. package/dist/core/stats-utils.cjs.map +1 -0
  126. package/dist/core/stats-utils.d.cts +50 -0
  127. package/dist/core/stats-utils.d.cts.map +1 -0
  128. package/dist/core/stats-utils.d.ts +50 -0
  129. package/dist/core/stats-utils.d.ts.map +1 -0
  130. package/dist/core/stats-utils.js +94 -0
  131. package/dist/core/stats-utils.js.map +1 -0
  132. package/dist/index.cjs +64 -0
  133. package/dist/index.cjs.map +1 -0
  134. package/dist/index.d.cts +22 -0
  135. package/dist/index.d.cts.map +1 -0
  136. package/dist/index.d.ts +22 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +30 -0
  139. package/dist/index.js.map +1 -0
  140. package/dist/progress/manager.cjs +325 -0
  141. package/dist/progress/manager.cjs.map +1 -0
  142. package/dist/progress/manager.d.cts +125 -0
  143. package/dist/progress/manager.d.cts.map +1 -0
  144. package/dist/progress/manager.d.ts +125 -0
  145. package/dist/progress/manager.d.ts.map +1 -0
  146. package/dist/progress/manager.js +321 -0
  147. package/dist/progress/manager.js.map +1 -0
  148. package/dist/reporters/csv.cjs +250 -0
  149. package/dist/reporters/csv.cjs.map +1 -0
  150. package/dist/reporters/csv.d.cts +92 -0
  151. package/dist/reporters/csv.d.cts.map +1 -0
  152. package/dist/reporters/csv.d.ts +92 -0
  153. package/dist/reporters/csv.d.ts.map +1 -0
  154. package/dist/reporters/csv.js +246 -0
  155. package/dist/reporters/csv.js.map +1 -0
  156. package/dist/reporters/human.cjs +516 -0
  157. package/dist/reporters/human.cjs.map +1 -0
  158. package/dist/reporters/human.d.cts +86 -0
  159. package/dist/reporters/human.d.cts.map +1 -0
  160. package/dist/reporters/human.d.ts +86 -0
  161. package/dist/reporters/human.d.ts.map +1 -0
  162. package/dist/reporters/human.js +509 -0
  163. package/dist/reporters/human.js.map +1 -0
  164. package/dist/reporters/index.cjs +17 -0
  165. package/dist/reporters/index.cjs.map +1 -0
  166. package/dist/reporters/index.d.cts +10 -0
  167. package/dist/reporters/index.d.cts.map +1 -0
  168. package/dist/reporters/index.d.ts +10 -0
  169. package/dist/reporters/index.d.ts.map +1 -0
  170. package/dist/reporters/index.js +10 -0
  171. package/dist/reporters/index.js.map +1 -0
  172. package/dist/reporters/json.cjs +215 -0
  173. package/dist/reporters/json.cjs.map +1 -0
  174. package/dist/reporters/json.d.cts +79 -0
  175. package/dist/reporters/json.d.cts.map +1 -0
  176. package/dist/reporters/json.d.ts +79 -0
  177. package/dist/reporters/json.d.ts.map +1 -0
  178. package/dist/reporters/json.js +211 -0
  179. package/dist/reporters/json.js.map +1 -0
  180. package/dist/reporters/registry.cjs +255 -0
  181. package/dist/reporters/registry.cjs.map +1 -0
  182. package/dist/reporters/registry.d.cts +155 -0
  183. package/dist/reporters/registry.d.cts.map +1 -0
  184. package/dist/reporters/registry.d.ts +155 -0
  185. package/dist/reporters/registry.d.ts.map +1 -0
  186. package/dist/reporters/registry.js +249 -0
  187. package/dist/reporters/registry.js.map +1 -0
  188. package/dist/reporters/simple.cjs +328 -0
  189. package/dist/reporters/simple.cjs.map +1 -0
  190. package/dist/reporters/simple.d.cts +51 -0
  191. package/dist/reporters/simple.d.cts.map +1 -0
  192. package/dist/reporters/simple.d.ts +51 -0
  193. package/dist/reporters/simple.d.ts.map +1 -0
  194. package/dist/reporters/simple.js +321 -0
  195. package/dist/reporters/simple.js.map +1 -0
  196. package/dist/schema/modestbench-config.schema.json +162 -0
  197. package/dist/storage/history.cjs +456 -0
  198. package/dist/storage/history.cjs.map +1 -0
  199. package/dist/storage/history.d.cts +99 -0
  200. package/dist/storage/history.d.cts.map +1 -0
  201. package/dist/storage/history.d.ts +99 -0
  202. package/dist/storage/history.d.ts.map +1 -0
  203. package/dist/storage/history.js +452 -0
  204. package/dist/storage/history.js.map +1 -0
  205. package/dist/types/cli.cjs +21 -0
  206. package/dist/types/cli.cjs.map +1 -0
  207. package/dist/types/cli.d.cts +296 -0
  208. package/dist/types/cli.d.cts.map +1 -0
  209. package/dist/types/cli.d.ts +296 -0
  210. package/dist/types/cli.d.ts.map +1 -0
  211. package/dist/types/cli.js +18 -0
  212. package/dist/types/cli.js.map +1 -0
  213. package/dist/types/core.cjs +14 -0
  214. package/dist/types/core.cjs.map +1 -0
  215. package/dist/types/core.d.cts +380 -0
  216. package/dist/types/core.d.cts.map +1 -0
  217. package/dist/types/core.d.ts +380 -0
  218. package/dist/types/core.d.ts.map +1 -0
  219. package/dist/types/core.js +13 -0
  220. package/dist/types/core.js.map +1 -0
  221. package/dist/types/index.cjs +27 -0
  222. package/dist/types/index.cjs.map +1 -0
  223. package/dist/types/index.d.cts +11 -0
  224. package/dist/types/index.d.cts.map +1 -0
  225. package/dist/types/index.d.ts +11 -0
  226. package/dist/types/index.d.ts.map +1 -0
  227. package/dist/types/index.js +11 -0
  228. package/dist/types/index.js.map +1 -0
  229. package/dist/types/interfaces.cjs +10 -0
  230. package/dist/types/interfaces.cjs.map +1 -0
  231. package/dist/types/interfaces.d.cts +381 -0
  232. package/dist/types/interfaces.d.cts.map +1 -0
  233. package/dist/types/interfaces.d.ts +381 -0
  234. package/dist/types/interfaces.d.ts.map +1 -0
  235. package/dist/types/interfaces.js +9 -0
  236. package/dist/types/interfaces.js.map +1 -0
  237. package/dist/types/utility.cjs +92 -0
  238. package/dist/types/utility.cjs.map +1 -0
  239. package/dist/types/utility.d.cts +330 -0
  240. package/dist/types/utility.d.cts.map +1 -0
  241. package/dist/types/utility.d.ts +330 -0
  242. package/dist/types/utility.d.ts.map +1 -0
  243. package/dist/types/utility.js +78 -0
  244. package/dist/types/utility.js.map +1 -0
  245. package/package.json +211 -0
  246. package/src/bootstrap.ts +35 -0
  247. package/src/cli/commands/history.ts +569 -0
  248. package/src/cli/commands/init.ts +658 -0
  249. package/src/cli/commands/run.ts +346 -0
  250. package/src/cli/index.ts +642 -0
  251. package/src/config/manager.ts +387 -0
  252. package/src/config/schema.ts +188 -0
  253. package/src/constants.ts +21 -0
  254. package/src/core/benchmark-schema.ts +185 -0
  255. package/src/core/engine.ts +888 -0
  256. package/src/core/engines/accurate-engine.ts +408 -0
  257. package/src/core/engines/index.ts +16 -0
  258. package/src/core/engines/tinybench-engine.ts +335 -0
  259. package/src/core/error-manager.ts +372 -0
  260. package/src/core/loader.ts +324 -0
  261. package/src/core/stats-utils.ts +135 -0
  262. package/src/index.ts +46 -0
  263. package/src/progress/manager.ts +415 -0
  264. package/src/reporters/csv.ts +368 -0
  265. package/src/reporters/human.ts +707 -0
  266. package/src/reporters/index.ts +10 -0
  267. package/src/reporters/json.ts +302 -0
  268. package/src/reporters/registry.ts +349 -0
  269. package/src/reporters/simple.ts +459 -0
  270. package/src/storage/history.ts +600 -0
  271. package/src/types/cli.ts +312 -0
  272. package/src/types/core.ts +414 -0
  273. package/src/types/index.ts +18 -0
  274. package/src/types/interfaces.ts +451 -0
  275. 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';