@fuzdev/fuz_util 0.42.0 → 0.43.0
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 +1 -1
- package/README.md +19 -12
- package/dist/async.d.ts +2 -2
- package/dist/async.d.ts.map +1 -1
- package/dist/async.js +2 -2
- package/dist/benchmark.d.ts +179 -0
- package/dist/benchmark.d.ts.map +1 -0
- package/dist/benchmark.js +400 -0
- package/dist/benchmark_baseline.d.ts +195 -0
- package/dist/benchmark_baseline.d.ts.map +1 -0
- package/dist/benchmark_baseline.js +415 -0
- package/dist/benchmark_format.d.ts +92 -0
- package/dist/benchmark_format.d.ts.map +1 -0
- package/dist/benchmark_format.js +327 -0
- package/dist/benchmark_stats.d.ts +112 -0
- package/dist/benchmark_stats.d.ts.map +1 -0
- package/dist/benchmark_stats.js +336 -0
- package/dist/benchmark_types.d.ts +174 -0
- package/dist/benchmark_types.d.ts.map +1 -0
- package/dist/benchmark_types.js +1 -0
- package/dist/library_json.d.ts +3 -3
- package/dist/library_json.d.ts.map +1 -1
- package/dist/library_json.js +1 -1
- package/dist/object.js +1 -1
- package/dist/stats.d.ts +126 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +262 -0
- package/dist/time.d.ts +161 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/time.js +260 -0
- package/dist/timings.d.ts +1 -7
- package/dist/timings.d.ts.map +1 -1
- package/dist/timings.js +16 -16
- package/package.json +21 -19
- package/src/lib/async.ts +3 -3
- package/src/lib/benchmark.ts +498 -0
- package/src/lib/benchmark_baseline.ts +573 -0
- package/src/lib/benchmark_format.ts +379 -0
- package/src/lib/benchmark_stats.ts +448 -0
- package/src/lib/benchmark_types.ts +197 -0
- package/src/lib/library_json.ts +3 -3
- package/src/lib/object.ts +1 -1
- package/src/lib/stats.ts +353 -0
- package/src/lib/time.ts +314 -0
- package/src/lib/timings.ts +17 -17
- package/src/lib/types.ts +2 -2
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Benchmark-specific statistical analysis.
|
|
3
|
+
* Uses the general stats utilities from stats.ts for timing/performance analysis.
|
|
4
|
+
* All timing values are in nanoseconds.
|
|
5
|
+
*/
|
|
6
|
+
import { TIME_NS_PER_SEC, time_format_adaptive } from './time.js';
|
|
7
|
+
import { stats_mean, stats_median, stats_std_dev, stats_percentile, stats_cv, stats_min_max, stats_confidence_interval, stats_outliers_mad, } from './stats.js';
|
|
8
|
+
/**
|
|
9
|
+
* Complete statistical analysis of timing measurements.
|
|
10
|
+
* Includes outlier detection, descriptive statistics, and performance metrics.
|
|
11
|
+
* All timing values are in nanoseconds.
|
|
12
|
+
*/
|
|
13
|
+
export class BenchmarkStats {
|
|
14
|
+
/** Mean (average) time in nanoseconds */
|
|
15
|
+
mean_ns;
|
|
16
|
+
/** Median time in nanoseconds */
|
|
17
|
+
median_ns;
|
|
18
|
+
/** Standard deviation in nanoseconds */
|
|
19
|
+
std_dev_ns;
|
|
20
|
+
/** Minimum time in nanoseconds */
|
|
21
|
+
min_ns;
|
|
22
|
+
/** Maximum time in nanoseconds */
|
|
23
|
+
max_ns;
|
|
24
|
+
/** 75th percentile in nanoseconds */
|
|
25
|
+
p75_ns;
|
|
26
|
+
/** 90th percentile in nanoseconds */
|
|
27
|
+
p90_ns;
|
|
28
|
+
/** 95th percentile in nanoseconds */
|
|
29
|
+
p95_ns;
|
|
30
|
+
/** 99th percentile in nanoseconds */
|
|
31
|
+
p99_ns;
|
|
32
|
+
/** Coefficient of variation (std_dev / mean) */
|
|
33
|
+
cv;
|
|
34
|
+
/** 95% confidence interval for the mean in nanoseconds */
|
|
35
|
+
confidence_interval_ns;
|
|
36
|
+
/** Array of detected outlier values in nanoseconds */
|
|
37
|
+
outliers_ns;
|
|
38
|
+
/** Ratio of outliers to total samples */
|
|
39
|
+
outlier_ratio;
|
|
40
|
+
/** Number of samples after outlier removal */
|
|
41
|
+
sample_size;
|
|
42
|
+
/** Original number of samples (before outlier removal) */
|
|
43
|
+
raw_sample_size;
|
|
44
|
+
/** Operations per second (NS_PER_SEC / mean_ns) */
|
|
45
|
+
ops_per_second;
|
|
46
|
+
/** Number of failed iterations (NaN, Infinity, or negative values) */
|
|
47
|
+
failed_iterations;
|
|
48
|
+
constructor(timings_ns) {
|
|
49
|
+
// Filter out invalid values (NaN, Infinity, negative)
|
|
50
|
+
const valid_timings = [];
|
|
51
|
+
let failed_count = 0;
|
|
52
|
+
for (const t of timings_ns) {
|
|
53
|
+
if (!isNaN(t) && isFinite(t) && t > 0) {
|
|
54
|
+
valid_timings.push(t);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
failed_count++;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
this.failed_iterations = failed_count;
|
|
61
|
+
this.raw_sample_size = timings_ns.length;
|
|
62
|
+
// If no valid timings, return empty stats
|
|
63
|
+
if (valid_timings.length === 0) {
|
|
64
|
+
this.mean_ns = NaN;
|
|
65
|
+
this.median_ns = NaN;
|
|
66
|
+
this.std_dev_ns = NaN;
|
|
67
|
+
this.min_ns = NaN;
|
|
68
|
+
this.max_ns = NaN;
|
|
69
|
+
this.p75_ns = NaN;
|
|
70
|
+
this.p90_ns = NaN;
|
|
71
|
+
this.p95_ns = NaN;
|
|
72
|
+
this.p99_ns = NaN;
|
|
73
|
+
this.cv = NaN;
|
|
74
|
+
this.confidence_interval_ns = [NaN, NaN];
|
|
75
|
+
this.outliers_ns = [];
|
|
76
|
+
this.outlier_ratio = 0;
|
|
77
|
+
this.sample_size = 0;
|
|
78
|
+
this.ops_per_second = 0;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Detect and remove outliers
|
|
82
|
+
const { cleaned, outliers } = stats_outliers_mad(valid_timings);
|
|
83
|
+
const sorted_cleaned = [...cleaned].sort((a, b) => a - b);
|
|
84
|
+
this.outliers_ns = outliers;
|
|
85
|
+
this.outlier_ratio = outliers.length / valid_timings.length;
|
|
86
|
+
this.sample_size = cleaned.length;
|
|
87
|
+
// Calculate statistics on cleaned data
|
|
88
|
+
this.mean_ns = stats_mean(cleaned);
|
|
89
|
+
this.median_ns = stats_median(sorted_cleaned);
|
|
90
|
+
this.std_dev_ns = stats_std_dev(cleaned, this.mean_ns);
|
|
91
|
+
const { min, max } = stats_min_max(sorted_cleaned);
|
|
92
|
+
this.min_ns = min;
|
|
93
|
+
this.max_ns = max;
|
|
94
|
+
this.p75_ns = stats_percentile(sorted_cleaned, 0.75);
|
|
95
|
+
this.p90_ns = stats_percentile(sorted_cleaned, 0.9);
|
|
96
|
+
this.p95_ns = stats_percentile(sorted_cleaned, 0.95);
|
|
97
|
+
this.p99_ns = stats_percentile(sorted_cleaned, 0.99);
|
|
98
|
+
this.cv = stats_cv(this.mean_ns, this.std_dev_ns);
|
|
99
|
+
this.confidence_interval_ns = stats_confidence_interval(cleaned);
|
|
100
|
+
// Calculate throughput (operations per second)
|
|
101
|
+
this.ops_per_second = this.mean_ns > 0 ? TIME_NS_PER_SEC / this.mean_ns : 0;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Format stats as a human-readable string.
|
|
105
|
+
*/
|
|
106
|
+
toString() {
|
|
107
|
+
return `BenchmarkStats(mean=${time_format_adaptive(this.mean_ns)}, ops/sec=${this.ops_per_second.toFixed(2)}, cv=${(this.cv * 100).toFixed(1)}%, samples=${this.sample_size})`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Compare two benchmark results for statistical significance.
|
|
112
|
+
* Uses Welch's t-test (handles unequal variances) and Cohen's d effect size.
|
|
113
|
+
*
|
|
114
|
+
* @param a - First benchmark stats (or any object with required properties)
|
|
115
|
+
* @param b - Second benchmark stats (or any object with required properties)
|
|
116
|
+
* @param options - Comparison options
|
|
117
|
+
* @returns Comparison result with significance, effect size, and recommendation
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* const comparison = benchmark_stats_compare(result_a.stats, result_b.stats);
|
|
122
|
+
* if (comparison.significant) {
|
|
123
|
+
* console.log(`${comparison.faster} is ${comparison.speedup_ratio.toFixed(2)}x faster`);
|
|
124
|
+
* }
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
export const benchmark_stats_compare = (a, b, options) => {
|
|
128
|
+
const alpha = options?.alpha ?? 0.05;
|
|
129
|
+
// Handle edge cases
|
|
130
|
+
if (a.sample_size === 0 || b.sample_size === 0) {
|
|
131
|
+
return {
|
|
132
|
+
faster: 'equal',
|
|
133
|
+
speedup_ratio: 1,
|
|
134
|
+
significant: false,
|
|
135
|
+
p_value: 1,
|
|
136
|
+
effect_size: 0,
|
|
137
|
+
effect_magnitude: 'negligible',
|
|
138
|
+
ci_overlap: true,
|
|
139
|
+
recommendation: 'Insufficient data for comparison',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
// Calculate speedup ratio (lower time = faster, so compare by time not ops/sec)
|
|
143
|
+
const speedup_ratio = a.mean_ns < b.mean_ns ? b.mean_ns / a.mean_ns : a.mean_ns / b.mean_ns;
|
|
144
|
+
const faster = a.mean_ns < b.mean_ns ? 'a' : a.mean_ns > b.mean_ns ? 'b' : 'equal';
|
|
145
|
+
// Welch's t-test (handles unequal variances)
|
|
146
|
+
// Special case: if both have zero variance, t-test is undefined
|
|
147
|
+
let p_value;
|
|
148
|
+
if (a.std_dev_ns === 0 && b.std_dev_ns === 0) {
|
|
149
|
+
// When there's no variance, any difference is 100% reliable (p=0) or identical (p=1)
|
|
150
|
+
p_value = a.mean_ns === b.mean_ns ? 1 : 0;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
const { t_statistic, degrees_of_freedom } = welch_t_test(a.mean_ns, a.std_dev_ns, a.sample_size, b.mean_ns, b.std_dev_ns, b.sample_size);
|
|
154
|
+
// Calculate two-tailed p-value using t-distribution approximation
|
|
155
|
+
p_value = t_distribution_p_value(Math.abs(t_statistic), degrees_of_freedom);
|
|
156
|
+
}
|
|
157
|
+
// Cohen's d effect size
|
|
158
|
+
const pooled_std_dev = Math.sqrt(((a.sample_size - 1) * a.std_dev_ns ** 2 + (b.sample_size - 1) * b.std_dev_ns ** 2) /
|
|
159
|
+
(a.sample_size + b.sample_size - 2));
|
|
160
|
+
// When pooled_std_dev is 0 but means differ, effect is maximal (infinite)
|
|
161
|
+
// When means are equal, effect is 0
|
|
162
|
+
let effect_size;
|
|
163
|
+
let effect_magnitude;
|
|
164
|
+
if (pooled_std_dev === 0) {
|
|
165
|
+
// Zero variance case - if means differ, it's a definitive difference
|
|
166
|
+
if (a.mean_ns === b.mean_ns) {
|
|
167
|
+
effect_size = 0;
|
|
168
|
+
effect_magnitude = 'negligible';
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// Any difference is 100% reliable when there's no variance
|
|
172
|
+
effect_size = Infinity;
|
|
173
|
+
effect_magnitude = 'large';
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
effect_size = Math.abs(a.mean_ns - b.mean_ns) / pooled_std_dev;
|
|
178
|
+
// Interpret effect size (Cohen's conventions)
|
|
179
|
+
effect_magnitude =
|
|
180
|
+
effect_size < 0.2
|
|
181
|
+
? 'negligible'
|
|
182
|
+
: effect_size < 0.5
|
|
183
|
+
? 'small'
|
|
184
|
+
: effect_size < 0.8
|
|
185
|
+
? 'medium'
|
|
186
|
+
: 'large';
|
|
187
|
+
}
|
|
188
|
+
// Check confidence interval overlap
|
|
189
|
+
const ci_overlap = a.confidence_interval_ns[0] <= b.confidence_interval_ns[1] &&
|
|
190
|
+
b.confidence_interval_ns[0] <= a.confidence_interval_ns[1];
|
|
191
|
+
// Determine significance
|
|
192
|
+
const significant = p_value < alpha;
|
|
193
|
+
// Generate recommendation
|
|
194
|
+
let recommendation;
|
|
195
|
+
if (!significant) {
|
|
196
|
+
recommendation =
|
|
197
|
+
effect_magnitude === 'negligible'
|
|
198
|
+
? 'No meaningful difference detected'
|
|
199
|
+
: `Difference not statistically significant (p=${p_value.toFixed(3)}), but effect size suggests ${effect_magnitude} practical difference`;
|
|
200
|
+
}
|
|
201
|
+
else if (effect_magnitude === 'negligible') {
|
|
202
|
+
recommendation = `Statistically significant but negligible practical difference (${speedup_ratio.toFixed(2)}x)`;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
recommendation = `${faster === 'a' ? 'First' : 'Second'} is ${speedup_ratio.toFixed(2)}x faster with ${effect_magnitude} effect size (p=${p_value.toFixed(3)})`;
|
|
206
|
+
}
|
|
207
|
+
// Adjust 'faster' to 'equal' if effect is negligible
|
|
208
|
+
const adjusted_faster = effect_magnitude === 'negligible' ? 'equal' : faster;
|
|
209
|
+
return {
|
|
210
|
+
faster: adjusted_faster,
|
|
211
|
+
speedup_ratio,
|
|
212
|
+
significant,
|
|
213
|
+
p_value,
|
|
214
|
+
effect_size,
|
|
215
|
+
effect_magnitude,
|
|
216
|
+
ci_overlap,
|
|
217
|
+
recommendation,
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Calculate Welch's t-test statistic and degrees of freedom.
|
|
222
|
+
* Welch's t-test is more robust than Student's t-test when variances are unequal.
|
|
223
|
+
*/
|
|
224
|
+
const welch_t_test = (mean1, std1, n1, mean2, std2, n2) => {
|
|
225
|
+
const var1 = std1 ** 2;
|
|
226
|
+
const var2 = std2 ** 2;
|
|
227
|
+
const se1 = var1 / n1;
|
|
228
|
+
const se2 = var2 / n2;
|
|
229
|
+
const t_statistic = (mean1 - mean2) / Math.sqrt(se1 + se2);
|
|
230
|
+
// Welch-Satterthwaite degrees of freedom
|
|
231
|
+
const numerator = (se1 + se2) ** 2;
|
|
232
|
+
const denominator = se1 ** 2 / (n1 - 1) + se2 ** 2 / (n2 - 1);
|
|
233
|
+
const degrees_of_freedom = numerator / denominator;
|
|
234
|
+
return { t_statistic, degrees_of_freedom };
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* Approximate p-value from t-distribution using the approximation formula.
|
|
238
|
+
* This avoids requiring a full t-distribution table or library.
|
|
239
|
+
* For large df (>30), this approximation is very accurate.
|
|
240
|
+
*/
|
|
241
|
+
const t_distribution_p_value = (t, df) => {
|
|
242
|
+
// Use normal approximation for large df
|
|
243
|
+
if (df > 100) {
|
|
244
|
+
// Standard normal CDF approximation
|
|
245
|
+
return 2 * (1 - normal_cdf(t));
|
|
246
|
+
}
|
|
247
|
+
// For smaller df, use a more accurate approximation
|
|
248
|
+
// Based on the incomplete beta function relationship
|
|
249
|
+
const x = df / (df + t * t);
|
|
250
|
+
const a = df / 2;
|
|
251
|
+
const b = 0.5;
|
|
252
|
+
// Approximation of regularized incomplete beta function
|
|
253
|
+
// This is accurate to about 4 decimal places for typical use cases
|
|
254
|
+
const beta_approx = incomplete_beta_approx(x, a, b);
|
|
255
|
+
return beta_approx;
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* Standard normal CDF approximation (Abramowitz and Stegun formula 7.1.26).
|
|
259
|
+
*/
|
|
260
|
+
const normal_cdf = (x) => {
|
|
261
|
+
const t = 1 / (1 + 0.2316419 * Math.abs(x));
|
|
262
|
+
const d = 0.3989423 * Math.exp((-x * x) / 2);
|
|
263
|
+
const p = d * t * (0.3193815 + t * (-0.3565638 + t * (1.781478 + t * (-1.821256 + t * 1.330274))));
|
|
264
|
+
return x > 0 ? 1 - p : p;
|
|
265
|
+
};
|
|
266
|
+
/**
|
|
267
|
+
* Approximate regularized incomplete beta function for p-value calculation.
|
|
268
|
+
* Uses continued fraction expansion for reasonable accuracy.
|
|
269
|
+
*/
|
|
270
|
+
const incomplete_beta_approx = (x, a, b) => {
|
|
271
|
+
// Simple approximation using the relationship between beta and normal distributions
|
|
272
|
+
// For our use case (t-distribution p-values), this provides sufficient accuracy
|
|
273
|
+
if (x <= 0)
|
|
274
|
+
return 0;
|
|
275
|
+
if (x >= 1)
|
|
276
|
+
return 1;
|
|
277
|
+
// Use symmetry if needed
|
|
278
|
+
if (x > (a + 1) / (a + b + 2)) {
|
|
279
|
+
return 1 - incomplete_beta_approx(1 - x, b, a);
|
|
280
|
+
}
|
|
281
|
+
// Continued fraction approximation (first few terms)
|
|
282
|
+
const lnBeta = ln_gamma(a) + ln_gamma(b) - ln_gamma(a + b);
|
|
283
|
+
const front = Math.exp(Math.log(x) * a + Math.log(1 - x) * b - lnBeta) / a;
|
|
284
|
+
// Simple continued fraction (limited iterations for speed)
|
|
285
|
+
let f = 1;
|
|
286
|
+
let c = 1;
|
|
287
|
+
let d = 0;
|
|
288
|
+
for (let m = 1; m <= 100; m++) {
|
|
289
|
+
const m2 = 2 * m;
|
|
290
|
+
// Even step
|
|
291
|
+
let aa = (m * (b - m) * x) / ((a + m2 - 1) * (a + m2));
|
|
292
|
+
d = 1 + aa * d;
|
|
293
|
+
if (Math.abs(d) < 1e-30)
|
|
294
|
+
d = 1e-30;
|
|
295
|
+
c = 1 + aa / c;
|
|
296
|
+
if (Math.abs(c) < 1e-30)
|
|
297
|
+
c = 1e-30;
|
|
298
|
+
d = 1 / d;
|
|
299
|
+
f *= d * c;
|
|
300
|
+
// Odd step
|
|
301
|
+
aa = (-(a + m) * (a + b + m) * x) / ((a + m2) * (a + m2 + 1));
|
|
302
|
+
d = 1 + aa * d;
|
|
303
|
+
if (Math.abs(d) < 1e-30)
|
|
304
|
+
d = 1e-30;
|
|
305
|
+
c = 1 + aa / c;
|
|
306
|
+
if (Math.abs(c) < 1e-30)
|
|
307
|
+
c = 1e-30;
|
|
308
|
+
d = 1 / d;
|
|
309
|
+
const delta = d * c;
|
|
310
|
+
f *= delta;
|
|
311
|
+
if (Math.abs(delta - 1) < 1e-8)
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
return front * f;
|
|
315
|
+
};
|
|
316
|
+
/**
|
|
317
|
+
* Log gamma function approximation (Lanczos approximation).
|
|
318
|
+
*/
|
|
319
|
+
const ln_gamma = (z) => {
|
|
320
|
+
const g = 7;
|
|
321
|
+
const c = [
|
|
322
|
+
0.99999999999980993, 676.5203681218851, -1259.1392167224028, 771.32342877765313,
|
|
323
|
+
-176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6,
|
|
324
|
+
1.5056327351493116e-7,
|
|
325
|
+
];
|
|
326
|
+
if (z < 0.5) {
|
|
327
|
+
return Math.log(Math.PI / Math.sin(Math.PI * z)) - ln_gamma(1 - z);
|
|
328
|
+
}
|
|
329
|
+
const z_adj = z - 1;
|
|
330
|
+
let x = c[0];
|
|
331
|
+
for (let i = 1; i < g + 2; i++) {
|
|
332
|
+
x += c[i] / (z_adj + i);
|
|
333
|
+
}
|
|
334
|
+
const t = z_adj + g + 0.5;
|
|
335
|
+
return 0.5 * Math.log(2 * Math.PI) + (z_adj + 0.5) * Math.log(t) - t + Math.log(x);
|
|
336
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { BenchmarkStats } from './benchmark_stats.js';
|
|
2
|
+
import type { Timer } from './time.js';
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for a benchmark suite.
|
|
5
|
+
*/
|
|
6
|
+
export interface BenchmarkConfig {
|
|
7
|
+
/**
|
|
8
|
+
* Target duration to run each benchmark task in milliseconds.
|
|
9
|
+
* The benchmark will run until this duration is reached or max_iterations is hit.
|
|
10
|
+
* Default: 1000ms
|
|
11
|
+
*/
|
|
12
|
+
duration_ms?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Number of warmup iterations before actual measurements.
|
|
15
|
+
* Warmup helps stabilize JIT compilation and caches.
|
|
16
|
+
* Default: 5
|
|
17
|
+
*/
|
|
18
|
+
warmup_iterations?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Cooldown time between tasks in milliseconds.
|
|
21
|
+
* Helps prevent interference between benchmarks.
|
|
22
|
+
* Default: 100ms
|
|
23
|
+
*/
|
|
24
|
+
cooldown_ms?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Minimum number of iterations to run.
|
|
27
|
+
* Default: 10
|
|
28
|
+
*/
|
|
29
|
+
min_iterations?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Maximum number of iterations to run.
|
|
32
|
+
* Prevents infinite loops if function is extremely fast.
|
|
33
|
+
* Default: 100000
|
|
34
|
+
*/
|
|
35
|
+
max_iterations?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Custom timer to use for measurements.
|
|
38
|
+
* Default: timer_default (auto-detects environment)
|
|
39
|
+
*/
|
|
40
|
+
timer?: Timer;
|
|
41
|
+
/**
|
|
42
|
+
* Callback invoked after each iteration completes.
|
|
43
|
+
* Useful for triggering garbage collection, logging progress, early termination,
|
|
44
|
+
* or custom instrumentation.
|
|
45
|
+
*
|
|
46
|
+
* **Note**: The callback time is NOT included in iteration measurements - it runs
|
|
47
|
+
* after the timing capture. However, frequent GC calls will slow overall benchmark
|
|
48
|
+
* execution time.
|
|
49
|
+
*
|
|
50
|
+
* @param task_name - Name of the current task being benchmarked
|
|
51
|
+
* @param iteration - Current iteration number (1-indexed)
|
|
52
|
+
* @param abort - Call to stop the benchmark early for this task
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* // Trigger GC between iterations (run node with --expose-gc)
|
|
57
|
+
* new Benchmark({
|
|
58
|
+
* on_iteration: () => {
|
|
59
|
+
* if (globalThis.gc) globalThis.gc();
|
|
60
|
+
* }
|
|
61
|
+
* })
|
|
62
|
+
*
|
|
63
|
+
* // Log progress for long-running benchmarks
|
|
64
|
+
* new Benchmark({
|
|
65
|
+
* on_iteration: (name, iteration) => {
|
|
66
|
+
* if (iteration % 1000 === 0) {
|
|
67
|
+
* console.log(`${name}: ${iteration} iterations`);
|
|
68
|
+
* }
|
|
69
|
+
* }
|
|
70
|
+
* })
|
|
71
|
+
*
|
|
72
|
+
* // Stop early when converged
|
|
73
|
+
* new Benchmark({
|
|
74
|
+
* on_iteration: (name, iteration, abort) => {
|
|
75
|
+
* if (iteration > 1000 && has_stabilized()) abort();
|
|
76
|
+
* }
|
|
77
|
+
* })
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
on_iteration?: (task_name: string, iteration: number, abort: () => void) => void;
|
|
81
|
+
/**
|
|
82
|
+
* Callback invoked after each task completes.
|
|
83
|
+
* Useful for logging progress during long benchmark runs.
|
|
84
|
+
*
|
|
85
|
+
* @param result - The completed benchmark result
|
|
86
|
+
* @param index - Zero-based index of the completed task
|
|
87
|
+
* @param total - Total number of tasks to run
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* new Benchmark({
|
|
92
|
+
* on_task_complete: (result, index, total) => {
|
|
93
|
+
* console.log(`[${index + 1}/${total}] ${result.name}: ${result.stats.ops_per_second.toFixed(0)} ops/sec`);
|
|
94
|
+
* }
|
|
95
|
+
* })
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
on_task_complete?: (result: BenchmarkResult, index: number, total: number) => void;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* A benchmark task to execute.
|
|
102
|
+
*/
|
|
103
|
+
export interface BenchmarkTask {
|
|
104
|
+
/** Name of the task (for display) */
|
|
105
|
+
name: string;
|
|
106
|
+
/** Function to benchmark (sync or async). Return values are ignored. */
|
|
107
|
+
fn: () => unknown;
|
|
108
|
+
/**
|
|
109
|
+
* Optional setup function run before benchmarking this task.
|
|
110
|
+
* Not included in timing measurements.
|
|
111
|
+
*/
|
|
112
|
+
setup?: () => void | Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Optional teardown function run after benchmarking this task.
|
|
115
|
+
* Not included in timing measurements.
|
|
116
|
+
*/
|
|
117
|
+
teardown?: () => void | Promise<void>;
|
|
118
|
+
/**
|
|
119
|
+
* If true, skip this task during benchmark runs.
|
|
120
|
+
* Useful for temporarily disabling tasks during development.
|
|
121
|
+
*/
|
|
122
|
+
skip?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* If true, run only this task (and other tasks marked `only`).
|
|
125
|
+
* Useful for focusing on specific tasks during development.
|
|
126
|
+
*/
|
|
127
|
+
only?: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Hint for whether the function is sync or async.
|
|
130
|
+
* If not provided, automatically detected during warmup.
|
|
131
|
+
* Setting this explicitly skips per-iteration promise checking for sync functions.
|
|
132
|
+
*/
|
|
133
|
+
async?: boolean;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Result from running a single benchmark task.
|
|
137
|
+
*/
|
|
138
|
+
export interface BenchmarkResult {
|
|
139
|
+
/** Task name */
|
|
140
|
+
name: string;
|
|
141
|
+
/** Statistical analysis of the benchmark */
|
|
142
|
+
stats: BenchmarkStats;
|
|
143
|
+
/** Number of iterations executed */
|
|
144
|
+
iterations: number;
|
|
145
|
+
/** Total time spent benchmarking (including warmup) in milliseconds */
|
|
146
|
+
total_time_ms: number;
|
|
147
|
+
/**
|
|
148
|
+
* Raw timing data for each iteration in nanoseconds.
|
|
149
|
+
* Useful for custom statistical analysis, histogram generation,
|
|
150
|
+
* or exporting to external tools.
|
|
151
|
+
*/
|
|
152
|
+
timings_ns: Array<number>;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Options for table formatting.
|
|
156
|
+
*/
|
|
157
|
+
export interface BenchmarkFormatTableOptions {
|
|
158
|
+
/**
|
|
159
|
+
* Group results by category using filter functions.
|
|
160
|
+
*/
|
|
161
|
+
groups?: Array<BenchmarkGroup>;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* A group definition for organizing benchmark results.
|
|
165
|
+
*/
|
|
166
|
+
export interface BenchmarkGroup {
|
|
167
|
+
/** Display name for the group */
|
|
168
|
+
name: string;
|
|
169
|
+
/** Optional description shown below the group name */
|
|
170
|
+
description?: string;
|
|
171
|
+
/** Filter function to determine which results belong to this group */
|
|
172
|
+
filter: (result: BenchmarkResult) => boolean;
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=benchmark_types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"benchmark_types.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/benchmark_types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,WAAW,CAAC;AAErC;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC;IAEd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAsCG;IACH,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IAEjF;;;;;;;;;;;;;;;;OAgBG;IACH,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnF;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IAEb,wEAAwE;IACxE,EAAE,EAAE,MAAM,OAAO,CAAC;IAElB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtC;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAC;IAEb,4CAA4C;IAC5C,KAAK,EAAE,cAAc,CAAC;IAEtB,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IAEnB,uEAAuE;IACvE,aAAa,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC3C;;OAEG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IAEb,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,sEAAsE;IACtE,MAAM,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC;CAC7C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/library_json.d.ts
CHANGED
|
@@ -10,11 +10,11 @@ import type { Url } from './url.js';
|
|
|
10
10
|
export interface LibraryJson {
|
|
11
11
|
package_json: PackageJson;
|
|
12
12
|
source_json: SourceJson;
|
|
13
|
-
/** Package name, e.g. `@
|
|
13
|
+
/** Package name, e.g. `@fuzdev/fuz_ui`. */
|
|
14
14
|
name: string;
|
|
15
15
|
/** Name without scope, e.g. `fuz`. */
|
|
16
16
|
repo_name: string;
|
|
17
|
-
/** GitHub repo URL, e.g. `https://github.com/
|
|
17
|
+
/** GitHub repo URL, e.g. `https://github.com/fuzdev/fuz_ui`. */
|
|
18
18
|
repo_url: Url;
|
|
19
19
|
/** GitHub user/org, e.g. `ryanatkn`. */
|
|
20
20
|
owner_name: string | null;
|
|
@@ -32,7 +32,7 @@ export interface LibraryJson {
|
|
|
32
32
|
*/
|
|
33
33
|
export declare const library_json_parse: (package_json: PackageJson, source_json: SourceJson) => LibraryJson;
|
|
34
34
|
/**
|
|
35
|
-
* Extracts repo name from a package name, e.g. `@
|
|
35
|
+
* Extracts repo name from a package name, e.g. `@fuzdev/fuz_ui` → `fuz`.
|
|
36
36
|
*/
|
|
37
37
|
export declare const library_repo_name_parse: (name: string) => string;
|
|
38
38
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"library_json.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/library_json.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,UAAU,CAAC;AAElC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,YAAY,EAAE,WAAW,CAAC;IAC1B,WAAW,EAAE,UAAU,CAAC;IACxB,
|
|
1
|
+
{"version":3,"file":"library_json.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/library_json.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,UAAU,CAAC;AAElC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,YAAY,EAAE,WAAW,CAAC;IAC1B,WAAW,EAAE,UAAU,CAAC;IACxB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,QAAQ,EAAE,GAAG,CAAC;IACd,wCAAwC;IACxC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,GAAG,GAAG,IAAI,CAAC;IACzB,6CAA6C;IAC7C,QAAQ,EAAE,GAAG,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,GAAG,GAAG,IAAI,CAAC;IACpB,aAAa,EAAE,GAAG,GAAG,IAAI,CAAC;IAC1B,sDAAsD;IACtD,SAAS,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAC9B,cAAc,WAAW,EACzB,aAAa,UAAU,KACrB,WAuDF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uBAAuB,GAAI,MAAM,MAAM,KAAG,MAStD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,WAAW,KAAG,MAAM,GAAG,IAQrE,CAAC"}
|
package/dist/library_json.js
CHANGED
|
@@ -49,7 +49,7 @@ export const library_json_parse = (package_json, source_json) => {
|
|
|
49
49
|
};
|
|
50
50
|
};
|
|
51
51
|
/**
|
|
52
|
-
* Extracts repo name from a package name, e.g. `@
|
|
52
|
+
* Extracts repo name from a package name, e.g. `@fuzdev/fuz_ui` → `fuz`.
|
|
53
53
|
*/
|
|
54
54
|
export const library_repo_name_parse = (name) => {
|
|
55
55
|
if (name[0] === '@') {
|
package/dist/object.js
CHANGED
|
@@ -65,7 +65,7 @@ export const reorder = (obj, keys) => {
|
|
|
65
65
|
/**
|
|
66
66
|
* Frozen empty object with no properties, good for options default values.
|
|
67
67
|
*/
|
|
68
|
-
export const EMPTY_OBJECT = Object.freeze({});
|
|
68
|
+
export const EMPTY_OBJECT = Object.freeze({});
|
|
69
69
|
/**
|
|
70
70
|
* Performs a depth-first traversal of an object's enumerable properties,
|
|
71
71
|
* calling `cb` for every key and value with the current `obj` context.
|