@soroush.tech/bench 0.1.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.
@@ -0,0 +1,1730 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ import { spawnSync } from "node:child_process";
4
+ import { pathToFileURL } from "node:url";
5
+ //#region \0rolldown/runtime.js
6
+ var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
7
+ //#endregion
8
+ //#region ../../node_modules/.pnpm/mitata@1.0.34/node_modules/mitata/src/lib.mjs
9
+ const AsyncFunction = (async () => {}).constructor;
10
+ const GeneratorFunction = (function* () {}).constructor;
11
+ const AsyncGeneratorFunction = (async function* () {}).constructor;
12
+ const $$1 = {
13
+ _: null,
14
+ __() {
15
+ return print($$1._);
16
+ }
17
+ };
18
+ async function measure(f, ...args) {
19
+ return await {
20
+ fn,
21
+ iter,
22
+ yield: generator,
23
+ [void 0]() {
24
+ throw new TypeError("expected iterator, generator or one-shot function");
25
+ }
26
+ }[kind(f)](f, ...args);
27
+ }
28
+ async function generator(gen, opts = {}) {
29
+ const g = gen({ get(name) {
30
+ return opts.args?.[name];
31
+ } });
32
+ const n = await g.next();
33
+ let $fn = n.value;
34
+ if (!n.value?.heap && null != n.value?.heap) opts.heap = false;
35
+ opts.concurrency ??= n.value?.concurrency ?? opts.args?.concurrency;
36
+ if (!n.value?.counters && null != n.value?.counters) opts.$counters = false;
37
+ if (n.done || "fn" !== kind($fn)) {
38
+ $fn = n.value?.bench || n.value?.manual;
39
+ if ("fn" !== kind($fn, true)) throw new TypeError("expected benchmarkable yield from generator");
40
+ opts.params ??= {};
41
+ const params = $fn.length;
42
+ opts.manual = !n.value.manual ? false : "manual" !== n.value.budget ? "real" : "manual";
43
+ for (let o = 0; o < params; o++) {
44
+ opts.params[o] = n.value[o];
45
+ if ("fn" !== kind(n.value[o])) throw new TypeError("expected function for benchmark parameter");
46
+ }
47
+ }
48
+ const stats = await fn($fn, opts);
49
+ if (!(await g.next()).done) throw new TypeError("expected generator to yield once");
50
+ return {
51
+ ...stats,
52
+ kind: "yield"
53
+ };
54
+ }
55
+ const print = (() => {
56
+ if (globalThis.console?.log) return globalThis.console.log;
57
+ if (globalThis.print && !globalThis.document) return globalThis.print;
58
+ return () => {
59
+ throw new Error("no print function available");
60
+ };
61
+ })();
62
+ const gc = (() => {
63
+ try {
64
+ return Bun.gc(true), () => Bun.gc(true);
65
+ } catch {}
66
+ try {
67
+ return globalThis.gc(), () => globalThis.gc();
68
+ } catch {}
69
+ try {
70
+ return globalThis.__gc(), () => globalThis.__gc();
71
+ } catch {}
72
+ try {
73
+ return globalThis.std.gc(), () => globalThis.std.gc();
74
+ } catch {}
75
+ try {
76
+ return globalThis.$262.gc(), () => globalThis.$262.gc();
77
+ } catch {}
78
+ try {
79
+ return globalThis.tjs.engine.gc.run(), () => globalThis.tjs.engine.gc.run();
80
+ } catch {}
81
+ return Object.assign(globalThis.Graal ? () => new Uint8Array(2 ** 29) : () => new Uint8Array(2 ** 30), { fallback: true });
82
+ })();
83
+ const now = (() => {
84
+ try {
85
+ Bun.nanoseconds();
86
+ return Bun.nanoseconds;
87
+ } catch {}
88
+ try {
89
+ $$1.agent.monotonicNow();
90
+ return () => 1e6 * $$1.agent.monotonicNow();
91
+ } catch {}
92
+ try {
93
+ $262.agent.monotonicNow();
94
+ return () => 1e6 * $262.agent.monotonicNow();
95
+ } catch {}
96
+ try {
97
+ const now = performance.now.bind(performance);
98
+ now();
99
+ return () => 1e6 * now();
100
+ } catch {
101
+ return () => 1e6 * Date.now();
102
+ }
103
+ })();
104
+ function kind(fn, _ = false) {
105
+ if (!(fn instanceof Function || fn instanceof AsyncFunction || fn instanceof GeneratorFunction || fn instanceof AsyncGeneratorFunction)) return;
106
+ if (fn instanceof GeneratorFunction || fn instanceof AsyncGeneratorFunction) return "yield";
107
+ if ((_ ? true : 0 === fn.length) && (fn instanceof Function || fn instanceof AsyncFunction)) return "fn";
108
+ if (0 !== fn.length && (fn instanceof Function || fn instanceof AsyncFunction)) return "iter";
109
+ }
110
+ const k_cpu_time_rescale_heap = 1.1;
111
+ const k_cpu_time_rescale_inner_gc = 2;
112
+ const k_max_samples = 1e9;
113
+ const k_batch_samples = 4096;
114
+ const k_batch_threshold = 65536;
115
+ const k_min_cpu_time = 642 * 1e6;
116
+ const k_warmup_threshold = 5e5;
117
+ function defaults$1(opts) {
118
+ opts.gc ??= gc;
119
+ opts.now ??= now;
120
+ opts.heap ??= null;
121
+ opts.params ??= {};
122
+ opts.manual ??= false;
123
+ opts.inner_gc ??= false;
124
+ opts.$counters ??= false;
125
+ opts.concurrency ??= 1;
126
+ opts.min_samples ??= 12;
127
+ opts.max_samples ??= k_max_samples;
128
+ opts.min_cpu_time ??= k_min_cpu_time;
129
+ opts.batch_unroll ??= 4;
130
+ opts.batch_samples ??= k_batch_samples;
131
+ opts.warmup_samples ??= 2;
132
+ opts.batch_threshold ??= k_batch_threshold;
133
+ opts.warmup_threshold ??= k_warmup_threshold;
134
+ opts.samples_threshold ??= 12;
135
+ if (opts.heap) opts.min_cpu_time *= k_cpu_time_rescale_heap;
136
+ if (opts.gc && opts.inner_gc) opts.min_cpu_time *= k_cpu_time_rescale_inner_gc;
137
+ }
138
+ async function fn(fn, opts = {}) {
139
+ defaults$1(opts);
140
+ let async = false;
141
+ let batch = false;
142
+ const params = Object.keys(opts.params);
143
+ warmup: {
144
+ const $p = new Array(params.length);
145
+ for (let o = 0; o < params.length; o++) $p[o] = await opts.params[o]();
146
+ const t0 = now();
147
+ const r = fn(...$p);
148
+ let t1 = now();
149
+ if (async = r instanceof Promise) await r, t1 = now();
150
+ if (t1 - t0 <= opts.warmup_threshold) for (let o = 0; o < opts.warmup_samples; o++) {
151
+ for (let oo = 0; oo < params.length; oo++) $p[oo] = await opts.params[oo]();
152
+ const t0 = now();
153
+ await fn(...$p);
154
+ if (batch = now() - t0 <= opts.batch_threshold) break;
155
+ }
156
+ }
157
+ if (opts.manual) {
158
+ batch = false;
159
+ opts.concurrency = 1;
160
+ }
161
+ const loop = new AsyncFunction("$fn", "$gc", "$now", "$heap", "$params", "$counters", `
162
+ ${!opts.$counters ? "" : "let _hc = false;"}
163
+ ${!opts.$counters ? "" : "try { $counters.init(); _hc = true; } catch {}"}
164
+
165
+ let _ = 0; let t = 0;
166
+ let samples = new Array(2 ** 20);
167
+ ${!opts.heap ? "" : "const heap = { _: 0, total: 0, min: Infinity, max: -Infinity };"}
168
+ ${!(opts.gc && opts.inner_gc && !opts.gc.fallback) ? "" : "const gc = { total: 0, min: Infinity, max: -Infinity };"}
169
+
170
+ ${!params.length ? "" : Array.from({ length: params.length }, (_, o) => `
171
+ ${Array.from({ length: opts.concurrency }, (_, c) => `
172
+ let param_${o}_${c} = ${!batch ? "null" : `new Array(${opts.batch_samples})`};
173
+ `.trim()).join(" ")}
174
+ `.trim()).join("\n")}
175
+
176
+ ${!opts.gc ? "" : `$gc();`}
177
+
178
+ for (; _ < ${opts.max_samples}; _++) {
179
+ if (_ >= ${opts.min_samples} && t >= ${opts.min_cpu_time}) break;
180
+
181
+ ${!params.length ? "" : `
182
+ ${!batch ? `
183
+ ${Array.from({ length: params.length }, (_, o) => `
184
+ ${Array.from({ length: opts.concurrency }, (_, c) => `
185
+ if ((param_${o}_${c} = $params[${o}]()) instanceof Promise) param_${o}_${c} = await param_${o}_${c};
186
+ `.trim()).join(" ")}
187
+ `.trim()).join("\n")}
188
+ ` : `
189
+ for (let o = 0; o < ${opts.batch_samples}; o++) {
190
+ ${Array.from({ length: params.length }, (_, o) => `
191
+ ${Array.from({ length: opts.concurrency }, (_, c) => `
192
+ if ((param_${o}_${c}[o] = $params[${o}]()) instanceof Promise) param_${o}_${c}[o] = await param_${o}_${c}[o];
193
+ `.trim()).join(" ")}
194
+ `.trim()).join("\n")}
195
+ }
196
+ `}
197
+ `}
198
+
199
+ ${!(opts.gc && opts.inner_gc) ? "" : `
200
+ igc: {
201
+ const t0 = $now();
202
+ $gc(); t += $now() - t0;
203
+ }
204
+ `}
205
+
206
+ ${!opts.manual ? "" : "let t2 = 0;"}
207
+ ${!opts.heap ? "" : "const h0 = $heap();"}
208
+ ${!opts.$counters ? "" : "if (_hc) try { $counters.before(); } catch {};"} const t0 = $now();
209
+
210
+ ${!batch ? `
211
+ ${!async ? "" : 1 >= opts.concurrency ? "" : "await Promise.all(["}
212
+ ${Array.from({ length: opts.concurrency }, (_, c) => `
213
+ ${!opts.manual ? "" : "t2 +="} ${!async ? "" : 1 < opts.concurrency ? "" : "await"} ${(!params.length ? `
214
+ $fn()
215
+ ` : `
216
+ $fn(${Array.from({ length: params.length }, (_, o) => `param_${o}_${c}`).join(", ")})
217
+ `).trim()}${!async ? ";" : 1 < opts.concurrency ? "," : ";"}
218
+ `.trim()).join("\n")}
219
+ ${!async ? "" : 1 >= opts.concurrency ? "" : `]);`}
220
+ ` : `
221
+ for (let o = 0; o < ${opts.batch_samples / opts.batch_unroll | 0}; o++) {
222
+ ${!params.length ? "" : `const param_offset = o * ${opts.batch_unroll};`}
223
+
224
+ ${Array.from({ length: opts.batch_unroll }, (_, u) => `
225
+ ${!async ? "" : 1 >= opts.concurrency ? "" : "await Promise.all(["}
226
+ ${Array.from({ length: opts.concurrency }, (_, c) => `
227
+ ${!async ? "" : 1 < opts.concurrency ? "" : "await"} ${(!params.length ? `
228
+ $fn()
229
+ ` : `
230
+ $fn(${Array.from({ length: params.length }, (_, o) => `param_${o}_${c}[${u === 0 ? "" : `${u} + `}param_offset]`).join(", ")})
231
+ `).trim()}${!async ? ";" : 1 < opts.concurrency ? "," : ";"}
232
+ `.trim()).join(" ")}
233
+ ${!async ? "" : 1 >= opts.concurrency ? "" : "]);"}
234
+ `.trim()).join("\n")}
235
+ }
236
+ `}
237
+
238
+ const t1 = $now();
239
+ ${!opts.$counters ? "" : "if (_hc) try { $counters.after(); } catch {};"}
240
+
241
+ ${!opts.heap ? "" : `
242
+ heap: {
243
+ const t0 = $now();
244
+ const h1 = ($heap() - h0) ${!batch ? "" : `/ ${opts.batch_samples}`}; t += $now() - t0;
245
+
246
+ if (0 <= h1) {
247
+ heap._++;
248
+ heap.total += h1;
249
+ heap.min = Math.min(h1, heap.min);
250
+ heap.max = Math.max(h1, heap.max);
251
+ }
252
+ }
253
+ `}
254
+
255
+ ${!(opts.gc && opts.inner_gc && !opts.gc.fallback) ? "" : `
256
+ igc: {
257
+ const t0 = $now();
258
+ $gc(); const t1 = $now() - t0;
259
+
260
+ t += t1;
261
+ gc.total += t1;
262
+ gc.min = Math.min(t1, gc.min);
263
+ gc.max = Math.max(t1, gc.max);
264
+ }
265
+ `};
266
+
267
+ const diff = ${opts.manual ? "t2" : "t1 - t0"};
268
+ t += ${"manual" === opts.manual ? "t2" : "t1 - t0"};
269
+ samples[_] = diff ${!batch ? "" : `/ ${opts.batch_samples}`};
270
+ }
271
+
272
+ samples.length = _;
273
+ samples.sort((a, b) => a - b);
274
+ if (samples.length > ${opts.samples_threshold}) samples = samples.slice(2, -2);
275
+
276
+ return {
277
+ samples,
278
+ min: samples[0],
279
+ max: samples[samples.length - 1],
280
+ p25: samples[(.25 * (samples.length - 1)) | 0],
281
+ p50: samples[(.50 * (samples.length - 1)) | 0],
282
+ p75: samples[(.75 * (samples.length - 1)) | 0],
283
+ p99: samples[(.99 * (samples.length - 1)) | 0],
284
+ p999: samples[(.999 * (samples.length - 1)) | 0],
285
+ avg: samples.reduce((a, v) => a + v, 0) / samples.length,
286
+ ticks: samples.length ${!batch ? "" : `* ${opts.batch_samples}`},
287
+ ${!opts.heap ? "" : "heap: { ...heap, avg: heap.total / heap._ },"}
288
+ ${!(opts.gc && opts.inner_gc && !opts.gc.fallback) ? "" : "gc: { ...gc, avg: gc.total / _ },"}
289
+ ${!opts.$counters ? "" : `...(!_hc ? {} : { counters: $counters.translate(${!batch ? 1 : opts.batch_samples}, _) }),`}
290
+ };
291
+
292
+ ${!opts.$counters ? "" : "if (_hc) try { $counters.deinit(); } catch {};"}
293
+ `);
294
+ return {
295
+ kind: "fn",
296
+ debug: loop.toString(),
297
+ ...await loop(fn, opts.gc, opts.now, opts.heap, opts.params, opts.$counters)
298
+ };
299
+ }
300
+ async function iter(iter, opts = {}) {
301
+ const _ = {};
302
+ defaults$1(opts);
303
+ let samples = new Array(2 ** 20);
304
+ const _i = { next() {
305
+ return _.next();
306
+ } };
307
+ const ctx = {
308
+ [Symbol.iterator]() {
309
+ return _i;
310
+ },
311
+ [Symbol.asyncIterator]() {
312
+ return _i;
313
+ },
314
+ get(name) {
315
+ return opts.args?.[name];
316
+ }
317
+ };
318
+ const gen = (function* () {
319
+ let batch = false;
320
+ warmup: {
321
+ const t0 = now();
322
+ yield void 0;
323
+ if (now() - t0 <= opts.warmup_threshold) for (let o = 0; o < opts.warmup_samples; o++) {
324
+ const t0 = now();
325
+ yield void 0;
326
+ if (batch = now() - t0 <= opts.batch_threshold) break;
327
+ }
328
+ }
329
+ const loop = new GeneratorFunction("$gc", "$now", "$samples", _.debug = `
330
+ let _ = 0; let t = 0;
331
+
332
+ ${!opts.gc ? "" : `$gc();`}
333
+
334
+ for (; _ < ${opts.max_samples}; _++) {
335
+ if (_ >= ${opts.min_samples} && t >= ${opts.min_cpu_time}) break;
336
+
337
+ ${!(opts.gc && opts.inner_gc) ? "" : `
338
+ let inner_gc_cost = 0;
339
+
340
+ igc: {
341
+ const t0 = $now(); $gc();
342
+ inner_gc_cost = $now() - t0;
343
+ }
344
+ `}
345
+
346
+ const t0 = $now();
347
+
348
+ ${!batch ? "yield void 0;" : `
349
+ for (let o = 0; o < ${opts.batch_samples / opts.batch_unroll | 0}; o++) {
350
+ ${new Array(opts.batch_unroll).fill("yield void 0;").join(" ")}
351
+ }
352
+ `}
353
+
354
+ const t1 = $now();
355
+ const diff = t1 - t0;
356
+
357
+ $samples[_] = diff ${!batch ? "" : `/ ${opts.batch_samples}`};
358
+ t += diff ${!(opts.gc && opts.inner_gc) ? "" : "+ inner_gc_cost"};
359
+ }
360
+
361
+ $samples.length = _;
362
+ `)(opts.gc, opts.now, samples);
363
+ _.batch = batch;
364
+ _.next = loop.next.bind(loop);
365
+ yield void 0;
366
+ })();
367
+ await iter((_.next = gen.next.bind(gen), ctx));
368
+ if (samples.length < opts.min_samples) throw new TypeError(`expected at least ${opts.min_samples} samples from iterator`);
369
+ samples.sort((a, b) => a - b);
370
+ if (samples.length > opts.samples_threshold) samples = samples.slice(2, -2);
371
+ return {
372
+ samples,
373
+ kind: "iter",
374
+ debug: _.debug,
375
+ min: samples[0],
376
+ max: samples[samples.length - 1],
377
+ p25: samples[.25 * (samples.length - 1) | 0],
378
+ p50: samples[.5 * (samples.length - 1) | 0],
379
+ p75: samples[.75 * (samples.length - 1) | 0],
380
+ p99: samples[.99 * (samples.length - 1) | 0],
381
+ p999: samples[.999 * (samples.length - 1) | 0],
382
+ avg: samples.reduce((a, v) => a + v, 0) / samples.length,
383
+ ticks: samples.length * (!_.batch ? 1 : opts.batch_samples)
384
+ };
385
+ }
386
+ //#endregion
387
+ //#region ../../node_modules/.pnpm/mitata@1.0.34/node_modules/mitata/src/main.mjs
388
+ let FLAGS = 0;
389
+ let $counters = null;
390
+ let COLLECTIONS = [{
391
+ id: 0,
392
+ name: null,
393
+ types: [],
394
+ trials: []
395
+ }];
396
+ const flags = {
397
+ compact: 1,
398
+ baseline: 2
399
+ };
400
+ var B = class {
401
+ f = null;
402
+ _args = {};
403
+ _name = "";
404
+ _group = 0;
405
+ _gc = "once";
406
+ flags = FLAGS;
407
+ _highlight = false;
408
+ constructor(name, f) {
409
+ this.f = f;
410
+ this.name(name);
411
+ if (!kind(f)) throw new TypeError("expected iterator, generator or one-shot function");
412
+ }
413
+ name(name, color = false) {
414
+ return this._name = name, this.highlight(color), this;
415
+ }
416
+ gc(gc = "once") {
417
+ if (![
418
+ true,
419
+ false,
420
+ "once",
421
+ "inner"
422
+ ].includes(gc)) throw new TypeError("invalid gc type");
423
+ return this._gc = gc, this;
424
+ }
425
+ highlight(color = false) {
426
+ if (!color) return this._highlight = false, this;
427
+ if (!$.colors.includes(color)) throw new TypeError("invalid highlight color");
428
+ return this._highlight = color, this;
429
+ }
430
+ compact(bool = true) {
431
+ if (bool) return this.flags |= flags.compact, this;
432
+ if (!bool) return this.flags &= ~flags.compact, this;
433
+ }
434
+ baseline(bool = true) {
435
+ if (bool) return this.flags |= flags.baseline, this;
436
+ if (!bool) return this.flags &= ~flags.baseline, this;
437
+ }
438
+ range(name, s, e, m = 8) {
439
+ const arr = [];
440
+ for (let o = s; o <= e; o *= m) arr.push(Math.min(o, e));
441
+ if (!arr.includes(e)) arr.push(e);
442
+ return this.args(name, arr);
443
+ }
444
+ dense_range(name, s, e, a = 1) {
445
+ const arr = [];
446
+ for (let o = s; o <= e; o += a) arr.push(o);
447
+ if (!arr.includes(e)) arr.push(e);
448
+ return this.args(name, arr);
449
+ }
450
+ args(name, args) {
451
+ if (name === null) return delete this._args.x, this;
452
+ if (Array.isArray(name)) return this._args.x = name, this;
453
+ if (null === args && "string" === typeof name) return delete this._args[name], this;
454
+ if (Array.isArray(args) && "string" === typeof name) return this._args[name] = args, this;
455
+ if (null !== name && "object" === typeof name) {
456
+ for (const key in name) {
457
+ const v = name[key];
458
+ if (v == null) delete this._args[key];
459
+ else if (Array.isArray(v)) this._args[key] = v;
460
+ else throw new TypeError("invalid arguments map value");
461
+ }
462
+ return this;
463
+ }
464
+ throw new TypeError("invalid arguments");
465
+ }
466
+ *_names() {
467
+ const args = Object.keys(this._args);
468
+ if ((0 === args.length ? "static" : 1 === args.length ? "args" : "multi-args") === "static") yield this._name;
469
+ else {
470
+ const offsets = new Array(args.length).fill(0);
471
+ const runs = args.reduce((len, name) => len * this._args[name].length, 1);
472
+ for (let o = 0; o < runs; o++) {
473
+ {
474
+ const _args = {};
475
+ let _name = this._name;
476
+ for (let oo = 0; oo < args.length; oo++) _args[args[oo]] = this._args[args[oo]][offsets[oo]];
477
+ for (let oo = 0; oo < args.length; oo++) _name = _name.replaceAll(`\$${args[oo]}`, _args[args[oo]]);
478
+ yield _name;
479
+ }
480
+ let offset = 0;
481
+ do
482
+ offsets[offset] = (1 + offsets[offset]) % this._args[args[offset]].length;
483
+ while (0 === offsets[offset++] && offset < args.length);
484
+ }
485
+ }
486
+ }
487
+ async run(thrw = false) {
488
+ const args = Object.keys(this._args);
489
+ const kind = 0 === args.length ? "static" : 1 === args.length ? "args" : "multi-args";
490
+ const tune = {
491
+ $counters,
492
+ inner_gc: "inner" === this._gc,
493
+ gc: !this._gc ? false : void 0,
494
+ heap: await (async () => {
495
+ if (globalThis.Bun) {
496
+ const { memoryUsage } = await import("bun:jsc");
497
+ return () => {
498
+ return memoryUsage().current;
499
+ };
500
+ }
501
+ try {
502
+ const { getHeapStatistics } = await import("node:v8");
503
+ getHeapStatistics();
504
+ return () => {
505
+ const m = getHeapStatistics();
506
+ return m.used_heap_size + m.malloced_memory;
507
+ };
508
+ } catch {}
509
+ })()
510
+ };
511
+ if (kind === "static") {
512
+ let stats, error;
513
+ try {
514
+ stats = await measure(this.f, tune);
515
+ } catch (err) {
516
+ error = err;
517
+ if (thrw) throw err;
518
+ }
519
+ return {
520
+ kind,
521
+ args: this._args,
522
+ alias: this._name,
523
+ group: this._group,
524
+ baseline: !!(this.flags & flags.baseline),
525
+ runs: [{
526
+ stats,
527
+ error,
528
+ args: {},
529
+ name: this._name
530
+ }],
531
+ style: {
532
+ highlight: this._highlight,
533
+ compact: !!(this.flags & flags.compact)
534
+ }
535
+ };
536
+ } else {
537
+ const offsets = new Array(args.length).fill(0);
538
+ const runs = new Array(args.reduce((len, name) => len * this._args[name].length, 1));
539
+ for (let o = 0; o < runs.length; o++) {
540
+ {
541
+ let stats, error;
542
+ const _args = {};
543
+ let _name = this._name;
544
+ for (let oo = 0; oo < args.length; oo++) _args[args[oo]] = this._args[args[oo]][offsets[oo]];
545
+ for (let oo = 0; oo < args.length; oo++) _name = _name.replaceAll(`\$${args[oo]}`, _args[args[oo]]);
546
+ try {
547
+ stats = await measure(this.f, {
548
+ ...tune,
549
+ args: _args
550
+ });
551
+ } catch (err) {
552
+ error = err;
553
+ if (thrw) throw err;
554
+ }
555
+ runs[o] = {
556
+ stats,
557
+ error,
558
+ args: _args,
559
+ name: _name
560
+ };
561
+ }
562
+ let offset = 0;
563
+ do
564
+ offsets[offset] = (1 + offsets[offset]) % this._args[args[offset]].length;
565
+ while (0 === offsets[offset++] && offset < args.length);
566
+ }
567
+ return {
568
+ runs,
569
+ kind,
570
+ args: this._args,
571
+ alias: this._name,
572
+ group: this._group,
573
+ baseline: !!(this.flags & flags.baseline),
574
+ style: {
575
+ highlight: this._highlight,
576
+ compact: !!(this.flags & flags.compact)
577
+ }
578
+ };
579
+ }
580
+ }
581
+ };
582
+ function bench(n, fn) {
583
+ if (typeof n === "function") fn = n, n = fn.name || "anonymous";
584
+ const collection = COLLECTIONS[COLLECTIONS.length - 1];
585
+ const b = new B(n, fn);
586
+ b._group = collection.id;
587
+ return collection.trials.push(b), b;
588
+ }
589
+ function colors() {
590
+ return globalThis.tjs?.env?.FORCE_COLOR || globalThis.process?.env?.FORCE_COLOR || !globalThis.Deno?.noColor && !globalThis.tjs?.env?.NO_COLOR && !globalThis.process?.env?.NO_COLOR && !globalThis.process?.env?.NODE_DISABLE_COLORS;
591
+ }
592
+ async function cpu() {
593
+ if (globalThis.process?.versions?.webcontainer) return null;
594
+ try {
595
+ let n;
596
+ if (n = __require("os")?.cpus?.()?.[0]?.model) return n;
597
+ } catch {}
598
+ try {
599
+ let n;
600
+ if (n = __require("node:os")?.cpus?.()?.[0]?.model) return n;
601
+ } catch {}
602
+ try {
603
+ let n;
604
+ if (n = globalThis.tjs?.system?.cpus?.[0]?.model) return n;
605
+ } catch {}
606
+ try {
607
+ let n;
608
+ if (n = (await import("node:os"))?.cpus?.()?.[0]?.model) return n;
609
+ } catch {}
610
+ return null;
611
+ }
612
+ function version() {
613
+ return {
614
+ v8: () => globalThis.version?.(),
615
+ bun: () => globalThis.Bun?.version,
616
+ "txiki.js": () => globalThis.tjs?.version,
617
+ deno: () => globalThis.Deno?.version?.deno,
618
+ llrt: () => globalThis.process?.versions?.llrt,
619
+ node: () => globalThis.process?.versions?.node,
620
+ graaljs: () => globalThis.Graal?.versionGraalVM,
621
+ webcontainer: () => globalThis.process?.versions?.webcontainer,
622
+ "quickjs-ng": () => globalThis.navigator?.userAgent?.split?.("/")[1],
623
+ hermes: () => globalThis.HermesInternal?.getRuntimeProperties?.()?.["OSS Release Version"]
624
+ }[runtime()]?.() || null;
625
+ }
626
+ function runtime() {
627
+ if (globalThis.d8) return "v8";
628
+ if (globalThis.tjs) return "txiki.js";
629
+ if (globalThis.Graal) return "graaljs";
630
+ if (globalThis.process?.versions?.llrt) return "llrt";
631
+ if (globalThis.process?.versions?.webcontainer) return "webcontainer";
632
+ if (globalThis.inIon && globalThis.performance?.mozMemory) return "spidermonkey";
633
+ if (globalThis.window && globalThis.netscape && globalThis.InternalError) return "firefox";
634
+ if (globalThis.window && globalThis.navigator && Error.prepareStackTrace) return "chromium";
635
+ if (globalThis.navigator?.userAgent?.toLowerCase?.()?.includes?.("quickjs-ng")) return "quickjs-ng";
636
+ if (globalThis.$262 && globalThis.lockdown && globalThis.AsyncDisposableStack) return "XS Moddable";
637
+ if (globalThis.$ && "IsHTMLDDA" in globalThis.$ && (/* @__PURE__ */ new Error()).stack.includes("runtime@")) return "jsc";
638
+ if (globalThis.window && globalThis.navigator && (/* @__PURE__ */ new Error()).stack.includes("runtime@")) return "webkit";
639
+ if (globalThis.os && globalThis.std) return "quickjs";
640
+ if (globalThis.Bun) return "bun";
641
+ if (globalThis.Deno) return "deno";
642
+ if (globalThis.HermesInternal) return "hermes";
643
+ if (globalThis.window && globalThis.navigator) return "browser";
644
+ if (globalThis.process) return "node";
645
+ else return null;
646
+ }
647
+ async function arch() {
648
+ if (runtime() === "webcontainer") return "js + wasm";
649
+ try {
650
+ let n;
651
+ if (n = Deno?.build?.target) return n;
652
+ } catch {}
653
+ try {
654
+ const os = await import("node:os");
655
+ return `${os.arch()}-${os.platform()}`;
656
+ } catch {}
657
+ if (globalThis.process?.arch && globalThis.process?.platform) return `${globalThis.process.arch}-${globalThis.process.platform}`;
658
+ if (runtime() === "txiki.js") return `${globalThis.tjs.system?.arch}-${globalThis.tjs.system?.platform}`;
659
+ if (runtime() === "spidermonkey") {
660
+ try {
661
+ const build = globalThis.getBuildConfiguration();
662
+ const platforms = [
663
+ "osx",
664
+ "linux",
665
+ "android",
666
+ "windows"
667
+ ];
668
+ const arch = [
669
+ "arm",
670
+ "x64",
671
+ "x86",
672
+ "wasi",
673
+ "arm64",
674
+ "mips32",
675
+ "mips64",
676
+ "loong64",
677
+ "riscv64"
678
+ ].find((k) => build[k]);
679
+ const platform = platforms.find((k) => build[k]);
680
+ if (arch) return !platform ? arch : `${arch}-${platform}`;
681
+ } catch {}
682
+ try {
683
+ if (globalThis.isAvxPresent()) return "x86_64";
684
+ } catch {}
685
+ }
686
+ return null;
687
+ }
688
+ function defaults(opts) {
689
+ opts.print ??= print;
690
+ opts.throw ??= false;
691
+ opts.filter ??= /.*/;
692
+ opts.format ??= "mitata";
693
+ opts.colors ??= colors();
694
+ opts.observe ??= (trial) => trial;
695
+ }
696
+ async function run(opts = {}) {
697
+ defaults(opts);
698
+ const t = Date.now();
699
+ const benchmarks = [];
700
+ const noop = await measure(() => {});
701
+ const _cpu = await measure(() => {}, { batch_unroll: 1 });
702
+ const noop_inner_gc = await measure(() => {}, { inner_gc: true });
703
+ const noop_iter = await measure((state) => {
704
+ for (const _ of state);
705
+ });
706
+ const context = {
707
+ now: t,
708
+ arch: await arch(),
709
+ version: version(),
710
+ runtime: runtime(),
711
+ cpu: {
712
+ name: await cpu(),
713
+ freq: 1 / _cpu.avg
714
+ },
715
+ noop: {
716
+ fn: noop,
717
+ iter: noop_iter,
718
+ fn_gc: noop_inner_gc
719
+ }
720
+ };
721
+ if (!$counters && context.arch?.includes?.("darwin") && [
722
+ "bun",
723
+ "node",
724
+ "deno"
725
+ ].includes(context.runtime)) try {
726
+ $counters = await import("@mitata/counters");
727
+ if (0 !== process.getuid()) throw $counters = false, 1;
728
+ } catch {}
729
+ if (!$counters && context.arch?.includes?.("linux") && [
730
+ "bun",
731
+ "node",
732
+ "deno"
733
+ ].includes(context.runtime)) try {
734
+ $counters = await import("@mitata/counters");
735
+ } catch (err) {
736
+ if (err?.message?.includes?.("PermissionDenied")) $counters = false;
737
+ }
738
+ const layout = COLLECTIONS.map((c) => ({
739
+ name: c.name,
740
+ types: c.types
741
+ }));
742
+ const format = "string" === typeof opts.format ? opts.format : Object.keys(opts.format)[0];
743
+ await formats[format](context, {
744
+ ...opts,
745
+ format: opts.format[format]
746
+ }, benchmarks, layout);
747
+ return COLLECTIONS = [{
748
+ name: 0,
749
+ types: [],
750
+ trials: []
751
+ }], {
752
+ layout,
753
+ context,
754
+ benchmarks
755
+ };
756
+ }
757
+ const formats = {
758
+ async quiet(_, opts, benchmarks) {
759
+ for (const collection of COLLECTIONS) for (const trial of collection.trials) if (opts.filter.test(trial._name)) benchmarks.push(opts.observe(await trial.run(opts.throw)));
760
+ },
761
+ async json(ctx, opts, benchmarks, layout) {
762
+ const print = opts.print;
763
+ const debug = opts.format?.debug ?? true;
764
+ const samples = opts.format?.samples ?? true;
765
+ for (const collection of COLLECTIONS) for (const trial of collection.trials) if (opts.filter.test(trial._name)) benchmarks.push(opts.observe(await trial.run(opts.throw)));
766
+ print(JSON.stringify({
767
+ layout,
768
+ benchmarks,
769
+ context: ctx
770
+ }, (k, v) => {
771
+ if (!debug && k === "debug") return "";
772
+ if (!samples && k === "samples") return null;
773
+ if (!(v instanceof Error)) return v;
774
+ return {
775
+ message: String(v.message),
776
+ stack: v.stack
777
+ };
778
+ }, 0));
779
+ },
780
+ async markdown(ctx, opts, benchmarks) {
781
+ let first = true;
782
+ const print = opts.print;
783
+ print(`clk: ~${ctx.cpu.freq.toFixed(2)} GHz`);
784
+ print(`cpu: ${ctx.cpu.name}`);
785
+ print(`runtime: ${ctx.runtime}${!ctx.version ? "" : ` ${ctx.version}`} (${ctx.arch})`);
786
+ print("");
787
+ for (const collection of COLLECTIONS) {
788
+ const trials = [];
789
+ if (!collection.trials.length) continue;
790
+ for (const trial of collection.trials) if (opts.filter.test(trial._name)) {
791
+ let bench = await trial.run(opts.throw);
792
+ bench = opts.observe(bench);
793
+ trials.push(bench);
794
+ benchmarks.push(bench);
795
+ }
796
+ if (!trials.length) continue;
797
+ if (!first) print("");
798
+ const name_len = trials.reduce((a, b) => Math.max(a, b.runs.reduce((a, b) => Math.max(a, b.name.length), 0)), 0);
799
+ print(`| ${(collection.name ? `• ${collection.name}` : !first ? "" : "benchmark").padEnd(name_len)} | ${"avg".padStart(16)} | ${"min".padStart(11)} | ${"p75".padStart(11)} | ${"p99".padStart(11)} | ${"max".padStart(11)} |`);
800
+ print(`| ${"-".repeat(name_len)} | ${"-".repeat(16)} | ${"-".repeat(11)} | ${"-".repeat(11)} | ${"-".repeat(11)} | ${"-".repeat(11)} |`);
801
+ first = false;
802
+ for (const trial of trials) for (const run of trial.runs) if (run.error) print(`| ${run.name.padEnd(name_len)} | error: ${run.error.message ?? run.error} |`);
803
+ else print(`| ${run.name.padEnd(name_len)} | \`${`${$.time(run.stats.avg)}/iter`.padStart(14)}\` | \`${$.time(run.stats.min).padStart(9)}\` | \`${$.time(run.stats.p75).padStart(9)}\` | \`${$.time(run.stats.p99).padStart(9)}\` | \`${$.time(run.stats.max).padStart(9)}\` |`);
804
+ }
805
+ },
806
+ async mitata(ctx, opts, benchmarks) {
807
+ const print = opts.print;
808
+ let k_legend = opts.format?.name ?? "longest";
809
+ if ("fixed" === k_legend) k_legend = 28;
810
+ else if (k_legend === "longest") {
811
+ k_legend = 28;
812
+ for (const collection of COLLECTIONS) for (const trial of collection.trials) if (opts.filter.test(trial._name)) for (const name of trial._names()) k_legend = Math.max(k_legend, name.length);
813
+ }
814
+ k_legend = Math.max(20, k_legend);
815
+ if (!opts.colors) print(`clk: ~${ctx.cpu.freq.toFixed(2)} GHz`);
816
+ else print($.gray + `clk: ~${ctx.cpu.freq.toFixed(2)} GHz` + $.reset);
817
+ if (!opts.colors) print(`cpu: ${ctx.cpu.name}`);
818
+ else print($.gray + `cpu: ${ctx.cpu.name}` + $.reset);
819
+ if (!opts.colors) print(`runtime: ${ctx.runtime}${!ctx.version ? "" : ` ${ctx.version}`} (${ctx.arch})`);
820
+ else print($.gray + `runtime: ${ctx.runtime}${!ctx.version ? "" : ` ${ctx.version}`} (${ctx.arch})` + $.reset);
821
+ print("");
822
+ print(`${"benchmark".padEnd(k_legend - 1)} avg (min … max) p75 / p99 (min … top 1%)`);
823
+ print("-".repeat(15 + k_legend) + " " + "-".repeat(31));
824
+ let first = true;
825
+ let optimized_out_warning = false;
826
+ for (const collection of COLLECTIONS) {
827
+ const trials = [];
828
+ let prev_run_gap = false;
829
+ if (!collection.trials.length) continue;
830
+ if (!collection.trials.some((trial) => opts.filter.test(trial._name))) continue;
831
+ else if (first) {
832
+ first = false;
833
+ if (collection.name) {
834
+ print(`• ${collection.name}`);
835
+ if (!opts.colors) print("-".repeat(15 + k_legend) + " " + "-".repeat(31));
836
+ else print($.gray + "-".repeat(15 + k_legend) + " " + "-".repeat(31) + $.reset);
837
+ }
838
+ } else {
839
+ print("");
840
+ if (collection.name) print(`• ${collection.name}`);
841
+ if (!opts.colors) print("-".repeat(15 + k_legend) + " " + "-".repeat(31));
842
+ else print($.gray + "-".repeat(15 + k_legend) + " " + "-".repeat(31) + $.reset);
843
+ }
844
+ for (const trial of collection.trials) if (opts.filter.test(trial._name)) {
845
+ let bench = await trial.run(opts.throw);
846
+ bench = opts.observe(bench);
847
+ trials.push([trial, bench]);
848
+ benchmarks.push(bench);
849
+ if (-1 === $.colors.indexOf(trial._highlight)) trial._highlight = null;
850
+ const _h = !opts.colors || !trial._highlight ? (x) => x : (x) => $[trial._highlight] + x + $.reset;
851
+ for (const r of bench.runs) {
852
+ if (prev_run_gap) print("");
853
+ if (r.error) if (!opts.colors) print(`${_h($.str(r.name, k_legend).padEnd(k_legend))} error: ${r.error.message ?? r.error}`);
854
+ else print(`${_h($.str(r.name, k_legend).padEnd(k_legend))} ${$.red + "error:" + $.reset} ${r.error.message ?? r.error}`);
855
+ else {
856
+ const compact = trial.flags & flags.compact;
857
+ const noop = "iter" === r.stats.kind ? ctx.noop.iter : trial._gc !== "inner" ? ctx.noop.fn : ctx.noop.fn_gc;
858
+ const optimized_out = r.stats.avg < 1.42 * noop.avg;
859
+ optimized_out_warning = optimized_out_warning || optimized_out;
860
+ if (compact) {
861
+ let l = "";
862
+ prev_run_gap = false;
863
+ const avg = $.time(r.stats.avg).padStart(9);
864
+ const name = $.str(r.name, k_legend).padEnd(k_legend);
865
+ l += _h(name) + " ";
866
+ if (!opts.colors) l += avg + "/iter";
867
+ else l += $.bold + $.yellow + avg + $.reset + $.bold + "/iter" + $.reset;
868
+ const p75 = $.time(r.stats.p75).padStart(9);
869
+ const p99 = $.time(r.stats.p99).padStart(9);
870
+ const bins = $.histogram.bins(r.stats, 11, .99);
871
+ const histogram = $.histogram.ascii(bins, 1, { colors: opts.colors });
872
+ l += " ";
873
+ if (!opts.colors) l += p75 + " " + p99 + " " + histogram[0];
874
+ else l += $.gray + p75 + " " + p99 + $.reset + " " + histogram[0];
875
+ if (optimized_out) if (!opts.colors) l += " !";
876
+ else l += $.red + " !" + $.reset;
877
+ print(l);
878
+ } else {
879
+ let l = "";
880
+ const avg = $.time(r.stats.avg).padStart(9);
881
+ const name = $.str(r.name, k_legend).padEnd(k_legend);
882
+ l += _h(name) + " ";
883
+ const p75 = $.time(r.stats.p75).padStart(9);
884
+ const bins = $.histogram.bins(r.stats, 21, .99);
885
+ const histogram = $.histogram.ascii(bins, r.stats.gc && r.stats.heap ? 2 : !(r.stats.gc || r.stats.heap) ? 2 : 3, { colors: opts.colors });
886
+ if (!opts.colors) l += avg + "/iter " + p75 + " " + histogram[0];
887
+ else l += $.bold + $.yellow + avg + $.reset + $.bold + "/iter" + $.reset + " " + $.gray + p75 + $.reset + " " + histogram[0];
888
+ if (optimized_out) if (!opts.colors) l += " !";
889
+ else l += $.red + " !" + $.reset;
890
+ print(l);
891
+ l = "";
892
+ const min = $.time(r.stats.min);
893
+ const max = $.time(r.stats.max);
894
+ const p99 = $.time(r.stats.p99).padStart(9);
895
+ const diff = 18 - (min.length + max.length);
896
+ l += " ".repeat(diff + k_legend - 8);
897
+ if (!opts.colors) l += "(" + min + " … " + max + ")";
898
+ else l += $.gray + "(" + $.reset + $.cyan + min + $.reset + $.gray + " … " + $.reset + $.magenta + max + $.reset + $.gray + ")" + $.reset;
899
+ l += " ";
900
+ if (!opts.colors) l += p99 + " " + histogram[1];
901
+ else l += $.gray + p99 + $.reset + " " + histogram[1];
902
+ print(l);
903
+ if (r.stats.gc) {
904
+ l = "";
905
+ prev_run_gap = true;
906
+ l += " ".repeat(k_legend - 10);
907
+ const gcm = $.time(r.stats.gc.min).padStart(9);
908
+ const gcx = $.time(r.stats.gc.max).padStart(9);
909
+ if (!opts.colors) l += "gc(" + gcm + " … " + gcx + ")";
910
+ else l += $.gray + "gc(" + $.reset + $.blue + gcm + $.reset + $.gray + " … " + $.reset + $.blue + gcx + $.reset + $.gray + ")" + $.reset;
911
+ if (r.stats.heap) {
912
+ l += " ";
913
+ const ha = $.bytes(r.stats.heap.avg).padStart(9);
914
+ const hm = $.bytes(r.stats.heap.min).padStart(9);
915
+ const hx = $.bytes(r.stats.heap.max).padStart(9);
916
+ if (!opts.colors) l += ha + " (" + hm + "…" + hx + ")";
917
+ else l += $.yellow + ha + $.reset + $.gray + " (" + $.reset + $.yellow + hm + $.reset + $.gray + "…" + $.reset + $.yellow + hx + $.reset + $.gray + ")" + $.reset;
918
+ } else {
919
+ l += " ";
920
+ const gca = $.time(r.stats.gc.avg).padStart(9);
921
+ if (!opts.colors) l += gca + " " + histogram[2];
922
+ else l += $.blue + gca + $.reset + " " + histogram[2];
923
+ }
924
+ print(l);
925
+ } else if (r.stats.heap) {
926
+ prev_run_gap = true;
927
+ l = " ".repeat(k_legend - 8);
928
+ const ha = $.bytes(r.stats.heap.avg).padStart(9);
929
+ const hm = $.bytes(r.stats.heap.min).padStart(9);
930
+ const hx = $.bytes(r.stats.heap.max).padStart(9);
931
+ if (!opts.colors) l += "(" + hm + " … " + hx + ") " + ha + " " + histogram[2];
932
+ else l += $.gray + "(" + $.reset + $.yellow + hm + $.reset + $.gray + " … " + $.reset + $.yellow + hx + $.reset + $.gray + ") " + $.reset + $.yellow + ha + $.reset + " " + histogram[2];
933
+ print(l);
934
+ }
935
+ if (r.stats.counters) {
936
+ l = "";
937
+ prev_run_gap = true;
938
+ if (ctx.arch.includes("linux")) {
939
+ const _bmispred = r.stats.counters._bmispred.avg;
940
+ const ipc = r.stats.counters.instructions.avg / r.stats.counters.cycles.avg;
941
+ const cache = 100 - Math.min(100, 100 * r.stats.counters.cache.misses.avg / r.stats.counters.cache.avg);
942
+ l += " ".repeat(k_legend - 12);
943
+ if (!opts.colors) l += $.amount(ipc).padStart(7) + " ipc";
944
+ else l += $.bold + $.green + $.amount(ipc).padStart(7) + $.reset + $.bold + " ipc" + $.reset;
945
+ if (!opts.colors) l += " (" + cache.toFixed(2).padStart(6) + "% cache)";
946
+ else l += $.gray + " (" + $.reset + (50 > cache ? $.red : 84 < cache ? $.green : $.yellow) + cache.toFixed(2).padStart(6) + "%" + $.reset + " cache" + $.gray + ")" + $.reset;
947
+ if (!opts.colors) l += " " + $.amount(_bmispred).padStart(7) + " branch misses";
948
+ else l += " " + $.green + $.amount(_bmispred).padStart(7) + $.reset + " branch misses";
949
+ print(l);
950
+ l = "";
951
+ l += " ".repeat(k_legend - 20);
952
+ if (opts.colors) l += $.gray;
953
+ l += $.amount(r.stats.counters.cycles.avg).padStart(7) + " cycles";
954
+ l += " " + $.amount(r.stats.counters.instructions.avg).padStart(7) + " instructions";
955
+ l += " " + $.amount(r.stats.counters.cache.avg).padStart(7) + " c-refs";
956
+ l += " " + $.amount(r.stats.counters.cache.misses.avg).padStart(7) + " c-misses";
957
+ if (opts.colors) l += $.reset;
958
+ print(l);
959
+ }
960
+ if (ctx.arch.includes("darwin")) {
961
+ const ipc = r.stats.counters.instructions.avg / r.stats.counters.cycles.avg;
962
+ const stalls = 100 * r.stats.counters.cycles.stalls.avg / r.stats.counters.cycles.avg;
963
+ const ldst = 100 * r.stats.counters.instructions.loads_and_stores.avg / r.stats.counters.instructions.avg;
964
+ const cache = 100 - Math.min(100, 100 * (r.stats.counters.l1.miss_loads.avg + r.stats.counters.l1.miss_stores.avg) / r.stats.counters.instructions.loads_and_stores.avg);
965
+ l += " ".repeat(k_legend - 13);
966
+ if (!opts.colors) l += $.amount(ipc).padStart(7) + " ipc";
967
+ else l += $.bold + $.green + $.amount(ipc).padStart(7) + $.reset + $.bold + " ipc" + $.reset;
968
+ if (!opts.colors) l += " (" + stalls.toFixed(2).padStart(6) + "% stalls)";
969
+ else l += $.gray + " (" + $.reset + (12 > stalls ? $.green : 50 < stalls ? $.red : $.yellow) + stalls.toFixed(2).padStart(6) + "%" + $.reset + " stalls" + $.gray + ")" + $.reset;
970
+ if (!opts.colors) l += " " + cache.toFixed(2).padStart(6) + "% L1 data cache";
971
+ else l += " " + (50 > cache ? $.red : 84 < cache ? $.green : $.yellow) + cache.toFixed(2).padStart(6) + "%" + $.reset + " L1 data cache";
972
+ print(l);
973
+ l = "";
974
+ l += " ".repeat(k_legend - 20);
975
+ if (opts.colors) l += $.gray;
976
+ l += $.amount(r.stats.counters.cycles.avg).padStart(7) + " cycles";
977
+ l += " " + $.amount(r.stats.counters.instructions.avg).padStart(7) + " instructions";
978
+ l += " " + ldst.toFixed(2).padStart(6) + "% retired LD/ST (" + $.amount(r.stats.counters.instructions.loads_and_stores.avg).padStart(7) + ")";
979
+ if (opts.colors) l += $.reset;
980
+ print(l);
981
+ }
982
+ }
983
+ }
984
+ }
985
+ }
986
+ }
987
+ if (collection.types.includes("b")) {
988
+ const map = {};
989
+ const colors = {};
990
+ for (const [trial, bench] of trials) for (const r of bench.runs) {
991
+ if (r.error) continue;
992
+ map[r.name] = r.stats.avg;
993
+ colors[r.name] = $[trial._highlight];
994
+ }
995
+ if (Object.keys(map).length) {
996
+ print("");
997
+ $.barplot.ascii(map, k_legend, 44, {
998
+ steps: -10,
999
+ colors: !opts.colors ? null : colors
1000
+ }).forEach((l) => print(l));
1001
+ }
1002
+ }
1003
+ if (collection.types.includes("x")) {
1004
+ const map = {};
1005
+ const colors = {};
1006
+ if (1 === trials.length) for (const [trial, bench] of trials) for (const r of bench.runs) {
1007
+ map[r.name] = r.stats;
1008
+ colors[r.name] = $[trial._highlight];
1009
+ }
1010
+ else for (const [trial, bench] of trials) {
1011
+ const runs = bench.runs.filter((r) => r.stats);
1012
+ if (!runs.length) continue;
1013
+ if (1 === runs.length) {
1014
+ map[runs[0].name] = runs[0].stats;
1015
+ colors[runs[0].name] = $[trial._highlight];
1016
+ } else {
1017
+ const stats = {
1018
+ avg: 0,
1019
+ min: Infinity,
1020
+ p25: Infinity,
1021
+ p75: -Infinity,
1022
+ p99: -Infinity
1023
+ };
1024
+ for (const r of runs) {
1025
+ stats.avg += r.stats.avg;
1026
+ stats.min = Math.min(stats.min, r.stats.min);
1027
+ stats.p25 = Math.min(stats.p25, r.stats.p25);
1028
+ stats.p75 = Math.max(stats.p75, r.stats.p75);
1029
+ stats.p99 = Math.max(stats.p99, r.stats.p99);
1030
+ }
1031
+ map[bench.alias] = stats;
1032
+ stats.avg /= runs.length;
1033
+ colors[bench.alias] = $[trial._highlight];
1034
+ }
1035
+ }
1036
+ if (Object.keys(map).length) {
1037
+ print("");
1038
+ $.boxplot.ascii(map, k_legend, 44, { colors: !opts.colors ? null : colors }).forEach((l) => print(l));
1039
+ }
1040
+ }
1041
+ if (collection.types.includes("l")) {
1042
+ const map = {};
1043
+ const extra = {};
1044
+ const colors = {};
1045
+ const labels = {};
1046
+ if (1 === trials.length) for (const [trial, bench] of trials) {
1047
+ const runs = bench.runs.filter((r) => r.stats);
1048
+ if (!runs.length) continue;
1049
+ if (1 === runs.length) {
1050
+ const { min, max, avg, peak, bins } = $.histogram.bins(runs[0].stats, 44, .99);
1051
+ extra.ymax = peak;
1052
+ colors.xmin = $.cyan;
1053
+ colors.xmax = $.magenta;
1054
+ extra.ymin = $.min(bins);
1055
+ labels.xmin = $.time(min);
1056
+ labels.xmax = $.time(max);
1057
+ extra.xmax = bins.length - 1;
1058
+ colors[runs[0].name] = $[trial._highlight] || $.bold;
1059
+ map[runs[0].name] = {
1060
+ y: bins,
1061
+ x: bins.map((_, o) => o),
1062
+ format(x, y, s) {
1063
+ x = Math.round(x * 44);
1064
+ if (!opts.colors) return s;
1065
+ if (x === avg) return $.yellow + s + $.reset;
1066
+ return (x < avg ? $.cyan : $.magenta) + s + $.reset;
1067
+ }
1068
+ };
1069
+ } else {
1070
+ const avgs = runs.map((r) => r.stats.avg);
1071
+ colors.ymin = $.cyan;
1072
+ colors.ymax = $.magenta;
1073
+ extra.ymin = $.min(avgs);
1074
+ extra.ymax = $.max(avgs);
1075
+ extra.xmax = runs.length - 1;
1076
+ labels.ymin = $.time(extra.ymin);
1077
+ labels.ymax = $.time(extra.ymax);
1078
+ colors[bench.alias] = $[trial._highlight];
1079
+ map[bench.alias] = {
1080
+ y: avgs,
1081
+ x: avgs.map((_, o) => o)
1082
+ };
1083
+ }
1084
+ }
1085
+ else if (trials.every(([_, bench]) => "static" === bench.kind)) {
1086
+ colors.xmin = $.cyan;
1087
+ colors.xmax = $.magenta;
1088
+ for (const [trial, bench] of trials) for (const r of bench.runs) {
1089
+ if (r.error) continue;
1090
+ const { bins, peak, steps } = $.histogram.bins(r.stats, 44, .99);
1091
+ const y = bins.map((b) => b / peak);
1092
+ map[r.name] = {
1093
+ y,
1094
+ x: steps
1095
+ };
1096
+ colors[r.name] = $[trial._highlight];
1097
+ extra.ymin = Math.min($.min(y), extra.ymin ?? Infinity);
1098
+ extra.ymax = Math.max($.max(y), extra.ymax ?? -Infinity);
1099
+ extra.xmin = Math.min($.min(steps), extra.xmin ?? Infinity);
1100
+ extra.xmax = Math.max($.max(steps), extra.xmax ?? -Infinity);
1101
+ labels.xmin = $.time(extra.xmin);
1102
+ labels.xmax = $.time(extra.xmax);
1103
+ }
1104
+ } else {
1105
+ let min = Infinity;
1106
+ let max = -Infinity;
1107
+ for (const [trial, bench] of trials) for (const r of bench.runs) {
1108
+ if (r.error) continue;
1109
+ min = Math.min(min, r.stats.avg);
1110
+ max = Math.max(max, r.stats.avg);
1111
+ }
1112
+ colors.ymin = $.cyan;
1113
+ colors.ymax = $.magenta;
1114
+ labels.ymin = $.time(min);
1115
+ labels.ymax = $.time(max);
1116
+ for (const [trial, bench] of trials) {
1117
+ const runs = bench.runs.filter((r) => r.stats);
1118
+ if (!runs.length) continue;
1119
+ if (1 === runs.length) {
1120
+ const y = runs[0].stats.avg / max;
1121
+ colors[runs[0].name] = $[trial._highlight];
1122
+ map[runs[0].name] = {
1123
+ x: [0, 1],
1124
+ y: [y, y]
1125
+ };
1126
+ extra.ymin = Math.min(y, extra.ymin ?? Infinity);
1127
+ extra.ymax = Math.max(y, extra.ymax ?? -Infinity);
1128
+ } else {
1129
+ colors[bench.alias] = $[trial._highlight];
1130
+ const y = runs.map((r) => r.stats.avg / max);
1131
+ extra.ymin = Math.min($.min(y), extra.ymin ?? Infinity);
1132
+ extra.ymax = Math.max($.max(y), extra.ymax ?? -Infinity);
1133
+ map[bench.alias] = {
1134
+ y,
1135
+ x: runs.map((_, o) => o / (runs.length - 1))
1136
+ };
1137
+ }
1138
+ }
1139
+ }
1140
+ if (Object.keys(map).length) {
1141
+ print("");
1142
+ $.lineplot.ascii(map, {
1143
+ labels,
1144
+ ...extra,
1145
+ width: 44,
1146
+ height: 16,
1147
+ key: k_legend,
1148
+ colors: !opts.colors ? null : colors
1149
+ }).forEach((l) => print(l));
1150
+ }
1151
+ }
1152
+ if (collection.types.includes("s")) {
1153
+ trials.sort((a, b) => {
1154
+ const aa = a[1].runs.filter((r) => r.stats);
1155
+ const bb = b[1].runs.filter((r) => r.stats);
1156
+ if (0 === aa.length) return 1;
1157
+ if (0 === bb.length) return -1;
1158
+ return aa.reduce((a, r) => a + r.stats.avg, 0) / aa.length - bb.reduce((a, r) => a + r.stats.avg, 0) / bb.length;
1159
+ });
1160
+ if (1 === trials.length) {
1161
+ const runs = trials[0][1].runs.filter((r) => r.stats).sort((a, b) => a.stats.avg - b.stats.avg);
1162
+ if (1 < runs.length) {
1163
+ print("");
1164
+ if (!opts.colors) print("summary");
1165
+ else print($.bold + "summary" + $.reset);
1166
+ if (!opts.colors) print(" " + runs[0].name);
1167
+ else print(" ".repeat(2) + $.bold + $.cyan + runs[0].name + $.reset);
1168
+ for (let o = 1; o < runs.length; o++) {
1169
+ const r = runs[o];
1170
+ const baseline = runs[0];
1171
+ const faster = r.stats.avg >= baseline.stats.avg;
1172
+ const diff = !faster ? Number((1 / r.stats.avg * baseline.stats.avg).toFixed(2)) : Number((1 / baseline.stats.avg * r.stats.avg).toFixed(2));
1173
+ if (!opts.colors) print(" ".repeat(3) + diff + `x ${faster ? "faster" : "slower"} than ${r.name}`);
1174
+ else print(" ".repeat(3) + (!faster ? $.red : $.green) + diff + $.reset + `x ${faster ? "faster" : "slower"} than ${$.bold + $.cyan + r.name + $.reset}`);
1175
+ }
1176
+ }
1177
+ } else {
1178
+ let header = false;
1179
+ const baseline = trials.find(([trial, bench]) => bench.baseline && bench.runs.some((r) => r.stats))?.[1] || trials[0][1];
1180
+ if (baseline) {
1181
+ const bruns = baseline.runs.filter((r) => !r.error).sort((a, b) => a.stats.avg - b.stats.avg);
1182
+ for (const [trial, bench] of trials) {
1183
+ if (bench === baseline) continue;
1184
+ const runs = bench.runs.filter((r) => !r.error).sort((a, b) => a.stats.avg - b.stats.avg);
1185
+ if (!runs.length) continue;
1186
+ if (!header) {
1187
+ print("");
1188
+ header = true;
1189
+ if (!opts.colors) print("summary");
1190
+ else print($.bold + "summary" + $.reset);
1191
+ if (1 !== bruns.length) if (!opts.colors) print(" " + baseline.alias);
1192
+ else print(" ".repeat(2) + $.bold + $.cyan + baseline.alias + $.reset);
1193
+ else if (!opts.colors) print(" " + bruns[0].name);
1194
+ else print(" ".repeat(2) + $.bold + $.cyan + bruns[0].name + $.reset);
1195
+ }
1196
+ if (1 === runs.length && 1 === bruns.length) {
1197
+ const r = runs[0];
1198
+ const br = bruns[0];
1199
+ const faster = r.stats.avg >= br.stats.avg;
1200
+ const diff = !faster ? Number((1 / r.stats.avg * br.stats.avg).toFixed(2)) : Number((1 / br.stats.avg * r.stats.avg).toFixed(2));
1201
+ if (!opts.colors) print(" ".repeat(3) + diff + `x ${faster ? "faster" : "slower"} than ${r.name}`);
1202
+ else print(" ".repeat(3) + (!faster ? $.red : $.green) + diff + $.reset + `x ${faster ? "faster" : "slower"} than ${$.bold + $.cyan + r.name + $.reset}`);
1203
+ } else {
1204
+ const rf = runs[0];
1205
+ const bf = bruns[0];
1206
+ const rs = runs[runs.length - 1];
1207
+ const bs = bruns[bruns.length - 1];
1208
+ const faster = runs.reduce((a, r) => a + r.stats.avg, 0) / runs.length >= bruns.reduce((a, r) => a + r.stats.avg, 0) / bruns.length;
1209
+ const sfaster = rs.stats.avg >= bs.stats.avg;
1210
+ const ffaster = rf.stats.avg >= bf.stats.avg;
1211
+ const sdiff = !sfaster ? Number((1 / rs.stats.avg * bs.stats.avg).toFixed(2)) : Number((1 / bs.stats.avg * rs.stats.avg).toFixed(2));
1212
+ const fdiff = !ffaster ? Number((1 / rf.stats.avg * bf.stats.avg).toFixed(2)) : Number((1 / bf.stats.avg * rf.stats.avg).toFixed(2));
1213
+ if (!opts.colors) print(" ".repeat(3) + (1 === sdiff ? sdiff : (sfaster ? "+" : "-") + sdiff) + "…" + (1 === fdiff ? fdiff : (ffaster ? "+" : "-") + fdiff) + `x ${faster ? "faster" : "slower"} than ${1 === runs.length ? rf.name : bench.alias}`);
1214
+ else print(" ".repeat(3) + (1 === sdiff ? $.gray + sdiff + $.reset : !sfaster ? $.red + "-" + sdiff + $.reset : $.green + "+" + sdiff + $.reset) + "…" + (1 === fdiff ? $.gray + fdiff + $.reset : !ffaster ? $.red + "-" + fdiff + $.reset : $.green + "+" + fdiff + $.reset) + `x ${faster ? "faster" : "slower"} than ${$.bold + $.cyan + (1 === runs.length ? rf.name : bench.alias) + $.reset}`);
1215
+ }
1216
+ }
1217
+ }
1218
+ }
1219
+ }
1220
+ }
1221
+ let nl = false;
1222
+ if (false === $counters) if (!opts.colors) print(""), nl = true, print("! = run with sudo to enable hardware counters");
1223
+ else print(""), nl = true, print($.yellow + "!" + $.reset + $.gray + " = " + $.reset + "run with sudo to enable hardware counters");
1224
+ if (optimized_out_warning) if (!opts.colors) nl || print(""), print(" ".repeat(k_legend - 13) + "benchmark was likely optimized out (dead code elimination) = !"), print(" ".repeat(k_legend - 13) + "https://github.com/evanwashere/mitata#writing-good-benchmarks");
1225
+ else nl || print(""), print(" ".repeat(k_legend - 13) + "benchmark was likely optimized out " + $.gray + "(dead code elimination)" + $.reset + $.gray + " = " + $.reset + $.red + "!" + $.reset), print(" ".repeat(k_legend - 13) + $.gray + "https://github.com/evanwashere/mitata#writing-good-benchmarks" + $.reset);
1226
+ }
1227
+ };
1228
+ const $ = {
1229
+ bold: "\x1B[1m",
1230
+ reset: "\x1B[0m",
1231
+ red: "\x1B[31m",
1232
+ cyan: "\x1B[36m",
1233
+ blue: "\x1B[34m",
1234
+ gray: "\x1B[90m",
1235
+ white: "\x1B[37m",
1236
+ black: "\x1B[30m",
1237
+ green: "\x1B[32m",
1238
+ yellow: "\x1B[33m",
1239
+ magenta: "\x1B[35m",
1240
+ colors: [
1241
+ "red",
1242
+ "cyan",
1243
+ "blue",
1244
+ "green",
1245
+ "yellow",
1246
+ "magenta",
1247
+ "gray",
1248
+ "white",
1249
+ "black"
1250
+ ],
1251
+ clamp(m, v, x) {
1252
+ return v < m ? m : v > x ? x : v;
1253
+ },
1254
+ min(arr, s = Infinity) {
1255
+ return arr.reduce((x, v) => Math.min(x, v), s);
1256
+ },
1257
+ max(arr, s = -Infinity) {
1258
+ return arr.reduce((x, v) => Math.max(x, v), s);
1259
+ },
1260
+ str(s, len = 3) {
1261
+ if (len >= s.length) return s;
1262
+ return `${s.slice(0, len - 2)}..`;
1263
+ },
1264
+ amount(n) {
1265
+ if (Number.isNaN(n)) return "NaN";
1266
+ if (n < 1e3) return n.toFixed(2);
1267
+ n /= 1e3;
1268
+ if (n < 1e3) return `${n.toFixed(2)}k`;
1269
+ n /= 1e3;
1270
+ if (n < 1e3) return `${n.toFixed(2)}M`;
1271
+ n /= 1e3;
1272
+ if (n < 1e3) return `${n.toFixed(2)}G`;
1273
+ n /= 1e3;
1274
+ if (n < 1e3) return `${n.toFixed(2)}T`;
1275
+ n /= 1e3;
1276
+ return `${n.toFixed(2)}P`;
1277
+ },
1278
+ bytes(b, pad = true) {
1279
+ if (Number.isNaN(b)) return "NaN";
1280
+ if (b < 1e3) return `${b.toFixed(2)} ${!pad ? "" : " "}b`;
1281
+ b /= 1024;
1282
+ if (b < 1e3) return `${b.toFixed(2)} kb`;
1283
+ b /= 1024;
1284
+ if (b < 1e3) return `${b.toFixed(2)} mb`;
1285
+ b /= 1024;
1286
+ if (b < 1e3) return `${b.toFixed(2)} gb`;
1287
+ b /= 1024;
1288
+ if (b < 1e3) return `${b.toFixed(2)} tb`;
1289
+ b /= 1024;
1290
+ return `${b.toFixed(2)} pb`;
1291
+ },
1292
+ time(ns) {
1293
+ if (ns < 1) return `${(ns * 1e3).toFixed(2)} ps`;
1294
+ if (ns < 1e3) return `${ns.toFixed(2)} ns`;
1295
+ ns /= 1e3;
1296
+ if (ns < 1e3) return `${ns.toFixed(2)} µs`;
1297
+ ns /= 1e3;
1298
+ if (ns < 1e3) return `${ns.toFixed(2)} ms`;
1299
+ ns /= 1e3;
1300
+ if (ns < 1e3) return `${ns.toFixed(2)} s`;
1301
+ ns /= 60;
1302
+ if (ns < 1e3) return `${ns.toFixed(2)} m`;
1303
+ ns /= 60;
1304
+ return `${ns.toFixed(2)} h`;
1305
+ },
1306
+ barplot: {
1307
+ symbols: {
1308
+ bar: "■",
1309
+ legend: "┤",
1310
+ tl: "┌",
1311
+ tr: "┐",
1312
+ bl: "└",
1313
+ br: "┘"
1314
+ },
1315
+ ascii(map, key = 8, size = 14, { steps = 0, fmt = $.time, colors = true, symbols = $.barplot.symbols } = {}) {
1316
+ const values = Object.values(map);
1317
+ const canvas = new Array(2 + values.length).fill("");
1318
+ steps += size;
1319
+ const min = $.min(values);
1320
+ const step = ($.max(values) - min) / steps;
1321
+ canvas[0] += " ".repeat(1 + key);
1322
+ canvas[0] += symbols.tl + " ".repeat(size) + symbols.tr;
1323
+ Object.keys(map).forEach((name, o) => {
1324
+ const value = map[name];
1325
+ const bars = Math.round((value - min) / step);
1326
+ if (colors?.[name]) canvas[o + 1] += colors[name];
1327
+ canvas[o + 1] += $.str(name, key).padStart(key);
1328
+ if (colors?.[name]) canvas[o + 1] += $.reset;
1329
+ canvas[o + 1] += " " + symbols.legend;
1330
+ if (colors) canvas[o + 1] += $.gray;
1331
+ canvas[o + 1] += symbols.bar.repeat(bars);
1332
+ if (colors) canvas[o + 1] += $.reset;
1333
+ canvas[o + 1] += " ";
1334
+ if (colors) canvas[o + 1] += $.yellow;
1335
+ canvas[o + 1] += fmt(value);
1336
+ if (colors) canvas[o + 1] += $.reset;
1337
+ });
1338
+ canvas[canvas.length - 1] += " ".repeat(1 + key);
1339
+ canvas[canvas.length - 1] += symbols.bl + " ".repeat(size) + symbols.br;
1340
+ return canvas;
1341
+ }
1342
+ },
1343
+ canvas: { braille(width, height) {
1344
+ const vwidth = 2 * width;
1345
+ const vheight = 4 * height;
1346
+ const buffer = new Uint8Array(vwidth * vheight);
1347
+ const symbols = [
1348
+ 10241,
1349
+ 10242,
1350
+ 10244,
1351
+ 10304,
1352
+ 10248,
1353
+ 10256,
1354
+ 10272,
1355
+ 10368
1356
+ ];
1357
+ return {
1358
+ buffer,
1359
+ width,
1360
+ height,
1361
+ vwidth,
1362
+ vheight,
1363
+ set(x, y, tag = 1) {
1364
+ buffer[x + y * vwidth] = tag;
1365
+ },
1366
+ line(s, e, tag = 1) {
1367
+ s.x = Math.round(s.x);
1368
+ s.y = Math.round(s.y);
1369
+ e.x = Math.round(e.x);
1370
+ e.y = Math.round(e.y);
1371
+ const dx = Math.abs(e.x - s.x);
1372
+ const dy = Math.abs(e.y - s.y);
1373
+ let err = dx - dy;
1374
+ let x = s.x;
1375
+ let y = s.y;
1376
+ const sx = s.x < e.x ? 1 : -1;
1377
+ const sy = s.y < e.y ? 1 : -1;
1378
+ while (true) {
1379
+ buffer[x + y * vwidth] = tag;
1380
+ if (x === e.x && y === e.y) break;
1381
+ const e2 = 2 * err;
1382
+ if (e2 < dx) y += sy, err += dx;
1383
+ if (e2 > -dy) x += sx, err -= dy;
1384
+ }
1385
+ },
1386
+ toString({ background = false, format = (x, y, s, tag, backgorund) => s } = {}) {
1387
+ const canvas = new Array(height).fill("");
1388
+ for (let y = 0; y < vheight; y += 4) {
1389
+ const y0 = y * vwidth;
1390
+ const y1 = y0 + vwidth;
1391
+ const y2 = y1 + vwidth;
1392
+ const y3 = y2 + vwidth;
1393
+ for (let x = 0; x < vwidth; x += 2) {
1394
+ let c = 10240;
1395
+ if (buffer[x + y0]) c |= symbols[0];
1396
+ if (buffer[1 + x + y0]) c |= symbols[4];
1397
+ if (buffer[x + y1]) c |= symbols[1];
1398
+ if (buffer[1 + x + y1]) c |= symbols[5];
1399
+ if (buffer[x + y2]) c |= symbols[2];
1400
+ if (buffer[1 + x + y2]) c |= symbols[6];
1401
+ if (buffer[x + y3]) c |= symbols[3];
1402
+ if (buffer[1 + x + y3]) c |= symbols[7];
1403
+ if (c === 10240 && !background) canvas[y / 4] += " ";
1404
+ else canvas[y / 4] += format(x / (vwidth - 1), y / (vheight - 1), String.fromCharCode(c), buffer[x + y0] || buffer[1 + x + y0] || buffer[x + y1] || buffer[1 + x + y1] || buffer[x + y2] || buffer[1 + x + y2] || buffer[x + y3] || buffer[1 + x + y3], c === 10240);
1405
+ }
1406
+ }
1407
+ return canvas;
1408
+ }
1409
+ };
1410
+ } },
1411
+ lineplot: {
1412
+ symbols: {
1413
+ tl: "┌",
1414
+ tr: "┐",
1415
+ bl: "└",
1416
+ br: "┘"
1417
+ },
1418
+ ascii(map, { colors = true, xmin = 0, xmax = 1, ymin = 0, ymax = 1, symbols = $.lineplot.symbols, key = 8, width = 12, height = 12, labels = {
1419
+ xmin: null,
1420
+ xmax: null,
1421
+ ymin: null,
1422
+ ymax: null
1423
+ } } = {}) {
1424
+ const keys = Object.keys(map);
1425
+ const _canvas = $.canvas.braille(width, height);
1426
+ const xs = (_canvas.vwidth - 1) / (xmax - xmin);
1427
+ const ys = (_canvas.vheight - 1) / (ymax - ymin);
1428
+ const colorsv = Object.entries(colors).filter(([n]) => !Object.keys(labels).includes(n)).map(([_, v]) => v);
1429
+ const acolors = $.colors.filter((n) => !colorsv.includes($[n]));
1430
+ keys.forEach((name, k) => {
1431
+ const { x: xp, y: yp } = map[name];
1432
+ for (let o = 0; o < xp.length - 1; o++) {
1433
+ if (null == xp[o] || null == xp[o + 1]) continue;
1434
+ if (null == yp[o] || null == yp[o + 1]) continue;
1435
+ const s = {
1436
+ x: Math.round(xs * (xp[o] - xmin)),
1437
+ y: _canvas.vheight - 1 - Math.round(ys * (yp[o] - ymin))
1438
+ };
1439
+ const e = {
1440
+ x: Math.round(xs * (xp[o + 1] - xmin)),
1441
+ y: _canvas.vheight - 1 - Math.round(ys * (yp[o + 1] - ymin))
1442
+ };
1443
+ _canvas.line(s, e, 1 + k);
1444
+ }
1445
+ });
1446
+ const canvas = new Array(2 + _canvas.height).fill("");
1447
+ canvas[0] += " ".repeat(1 + key);
1448
+ canvas[0] += symbols.tl + " ".repeat(width) + symbols.tr;
1449
+ const lines = _canvas.toString({ format(x, y, s, tag) {
1450
+ const name = keys[tag - 1];
1451
+ if (map[name].format) return map[name].format(x, y, s);
1452
+ else if (colors?.[name]) return colors[name] + s + $.reset;
1453
+ else return $[acolors[(tag - 1) % acolors.length]] + s + $.reset;
1454
+ } });
1455
+ const plabels = {
1456
+ 0: !colors?.ymax ? labels.ymax || "" : colors.ymax + (labels.ymax || "") + $.reset,
1457
+ [lines.length - 1]: !colors?.ymin ? labels.ymin || "" : colors.ymin + (labels.ymin || "") + $.reset
1458
+ };
1459
+ const legends = keys.map((name, k) => {
1460
+ if (colors?.[name]) return colors[name] + $.str(name, key).padStart(key) + $.reset;
1461
+ else return $[acolors[k % acolors.length]] + $.str(name, key).padStart(key) + $.reset;
1462
+ });
1463
+ lines.forEach((l, o) => {
1464
+ canvas[o + 1] += legends[o] ?? " ".repeat(key);
1465
+ canvas[o + 1] += " ".repeat(2) + l + (!plabels[o] ? "" : " " + plabels[o]);
1466
+ });
1467
+ canvas[canvas.length - 1] += " ".repeat(1 + key);
1468
+ canvas[canvas.length - 1] += symbols.bl + " ".repeat(width) + symbols.br;
1469
+ if (labels.xmin || labels.xmax) {
1470
+ const xmin = labels.xmin || "";
1471
+ const xmax = labels.xmax || "";
1472
+ const gap = 2 + width - xmin.length;
1473
+ canvas.push(" ".repeat(key) + " " + (!colors?.xmin ? xmin : colors.xmin + xmin + $.reset) + (!colors?.xmax ? xmax.padStart(gap) : colors.xmax + xmax.padStart(gap) + $.reset));
1474
+ }
1475
+ return canvas;
1476
+ }
1477
+ },
1478
+ histogram: {
1479
+ symbols: [
1480
+ "▁",
1481
+ "▂",
1482
+ "▃",
1483
+ "▄",
1484
+ "▅",
1485
+ "▆",
1486
+ "▇",
1487
+ "█"
1488
+ ],
1489
+ bins(stats, size = 6, percentile = 1) {
1490
+ const offset = percentile * (stats.samples.length - 1) | 0;
1491
+ let min = stats.min;
1492
+ const max = stats.samples[offset] || stats.max || 1;
1493
+ const steps = new Array(size);
1494
+ const bins = new Array(size).fill(0);
1495
+ const step = (max - min) / (size - 1);
1496
+ if (0 === step) {
1497
+ min = 0;
1498
+ for (let o = 0; o < size; o++) steps[o] = o * step;
1499
+ bins[$.clamp(0, Math.round((stats.avg - min) / step), size - 1)] = 1;
1500
+ } else {
1501
+ for (let o = 0; o < size; o++) steps[o] = min + o * step;
1502
+ for (let o = 0; o <= offset; o++) bins[Math.round((stats.samples[o] - min) / step)]++;
1503
+ }
1504
+ return {
1505
+ min,
1506
+ max,
1507
+ step,
1508
+ bins,
1509
+ steps,
1510
+ peak: $.max(bins),
1511
+ outliers: stats.samples.length - 1 - offset,
1512
+ avg: $.clamp(0, Math.round((stats.avg - min) / step), size - 1)
1513
+ };
1514
+ },
1515
+ ascii(_bins, height = 1, { colors = true, symbols = $.histogram.symbols } = {}) {
1516
+ const canvas = new Array(height);
1517
+ const { avg, peak, bins } = _bins;
1518
+ const scale = (height * symbols.length - 1) / peak;
1519
+ for (let y = 0; y < height; y++) {
1520
+ let l = "";
1521
+ if (0 !== avg) {
1522
+ if (colors) l += $.cyan;
1523
+ for (let o = 0; o < avg; o++) {
1524
+ const b = bins[o];
1525
+ if (y === 0) l += symbols[$.clamp(0, Math.round(b * scale), symbols.length - 1)];
1526
+ else {
1527
+ const min = y * symbols.length;
1528
+ const max = (y + 1) * symbols.length;
1529
+ const offset = Math.round(b * scale) | 0;
1530
+ if (min >= offset) l += " ";
1531
+ else if (max <= offset) l += symbols[symbols.length - 1];
1532
+ else l += symbols[$.clamp(min, offset, max) % symbols.length];
1533
+ }
1534
+ }
1535
+ if (colors) l += $.reset;
1536
+ }
1537
+ {
1538
+ if (colors) l += $.yellow;
1539
+ const b = bins[avg];
1540
+ if (y === 0) l += symbols[$.clamp(0, Math.round(b * scale), symbols.length - 1)];
1541
+ else {
1542
+ const min = y * symbols.length;
1543
+ const max = (y + 1) * symbols.length;
1544
+ const offset = Math.round(b * scale) | 0;
1545
+ if (min >= offset) l += " ";
1546
+ else if (max <= offset) l += symbols[symbols.length - 1];
1547
+ else l += symbols[$.clamp(min, offset, max) % symbols.length];
1548
+ }
1549
+ if (colors) l += $.reset;
1550
+ }
1551
+ if (avg != bins.length - 1) {
1552
+ if (colors) l += $.magenta;
1553
+ for (let o = 1 + avg; o < bins.length; o++) {
1554
+ const b = bins[o];
1555
+ if (y === 0) l += symbols[$.clamp(0, Math.round(b * scale), symbols.length - 1)];
1556
+ else {
1557
+ const min = y * symbols.length;
1558
+ const max = (y + 1) * symbols.length;
1559
+ const offset = Math.round(b * scale) | 0;
1560
+ if (min >= offset) l += " ";
1561
+ else if (max <= offset) l += symbols[symbols.length - 1];
1562
+ else l += symbols[$.clamp(min, offset, max) % symbols.length];
1563
+ }
1564
+ }
1565
+ if (colors) l += $.reset;
1566
+ }
1567
+ canvas[y] = l;
1568
+ }
1569
+ return canvas.reverse();
1570
+ }
1571
+ },
1572
+ boxplot: {
1573
+ symbols: {
1574
+ v: "│",
1575
+ h: "─",
1576
+ tl: "┌",
1577
+ tr: "┐",
1578
+ bl: "└",
1579
+ br: "┘",
1580
+ avg: {
1581
+ top: "┬",
1582
+ middle: "│",
1583
+ bottom: "┴"
1584
+ },
1585
+ tail: {
1586
+ top: "╷",
1587
+ bottom: "╵",
1588
+ middle: ["├", "┤"]
1589
+ }
1590
+ },
1591
+ ascii(map, key = 8, size = 14, { fmt = $.time, colors = true, symbols = $.boxplot.symbols } = {}) {
1592
+ let tmin = Infinity;
1593
+ let tmax = -Infinity;
1594
+ const keys = Object.keys(map);
1595
+ const canvas = new Array(3 + 3 * keys.length).fill("");
1596
+ for (const name of keys) {
1597
+ const stats = map[name];
1598
+ if (tmin > stats.min) tmin = stats.min;
1599
+ const max = stats.p99 || stats.max || 1;
1600
+ if (max > tmax) tmax = max;
1601
+ }
1602
+ const steps = 2 + size;
1603
+ const step = (tmax - tmin) / (steps - 1);
1604
+ canvas[0] += " ".repeat(1 + key);
1605
+ canvas[0] += symbols.tl + " ".repeat(size) + symbols.tr;
1606
+ keys.forEach((name, o) => {
1607
+ o *= 3;
1608
+ const stats = map[name];
1609
+ const min = stats.min;
1610
+ const avg = stats.avg;
1611
+ const p25 = stats.p25;
1612
+ const p75 = stats.p75;
1613
+ const max = stats.p99 || stats.max || 1;
1614
+ const min_offset = 1 + Math.min(steps - 1, Math.round((min - tmin) / step));
1615
+ const max_offset = 1 + Math.min(steps - 1, Math.round((max - tmin) / step));
1616
+ const avg_offset = 1 + Math.min(steps - 1, Math.round((avg - tmin) / step));
1617
+ const p25_offset = 1 + Math.min(steps - 1, Math.round((p25 - tmin) / step));
1618
+ const p75_offset = 1 + Math.min(steps - 1, Math.round((p75 - tmin) / step));
1619
+ const u = new Array(2 + steps).fill(" ");
1620
+ const m = new Array(2 + steps).fill(" ");
1621
+ const l = new Array(2 + steps).fill(" ");
1622
+ u[0] = !colors ? "" : $.cyan;
1623
+ m[0] = !colors ? "" : $.cyan;
1624
+ l[0] = !colors ? "" : $.cyan;
1625
+ if (min_offset < p25_offset) {
1626
+ u[min_offset] = symbols.tail.top;
1627
+ l[min_offset] = symbols.tail.bottom;
1628
+ m[min_offset] = symbols.tail.middle[0];
1629
+ for (let o = 1 + min_offset; o < p25_offset; o++) m[o] = symbols.h;
1630
+ }
1631
+ if (avg_offset > p25_offset) {
1632
+ u[p25_offset] = symbols.tl;
1633
+ l[p25_offset] = symbols.bl;
1634
+ m[p25_offset] = min_offset === p25_offset ? symbols.v : symbols.tail.middle[1];
1635
+ for (let o = 1 + p25_offset; o < avg_offset; o++) u[o] = l[o] = symbols.h;
1636
+ }
1637
+ u[avg_offset] = !colors ? symbols.avg.top : $.reset + $.yellow + symbols.avg.top + $.reset + $.magenta;
1638
+ l[avg_offset] = !colors ? symbols.avg.bottom : $.reset + $.yellow + symbols.avg.bottom + $.reset + $.magenta;
1639
+ m[avg_offset] = !colors ? symbols.avg.middle : $.reset + $.yellow + symbols.avg.middle + $.reset + $.magenta;
1640
+ if (avg_offset < p75_offset) {
1641
+ u[p75_offset] = symbols.tr;
1642
+ l[p75_offset] = symbols.br;
1643
+ m[p75_offset] = max_offset === p75_offset ? symbols.v : symbols.tail.middle[0];
1644
+ for (let o = 1 + avg_offset; o < p75_offset; o++) u[o] = l[o] = symbols.h;
1645
+ }
1646
+ if (max_offset > p75_offset) {
1647
+ u[max_offset] = symbols.tail.top;
1648
+ l[max_offset] = symbols.tail.bottom;
1649
+ m[max_offset] = symbols.tail.middle[1];
1650
+ for (let o = 1 + Math.max(avg_offset, p75_offset); o < max_offset; o++) m[o] = symbols.h;
1651
+ }
1652
+ canvas[o + 1] = " ".repeat(1 + key) + u.join("").trimEnd() + (!colors ? "" : $.reset);
1653
+ if (colors?.[name]) canvas[o + 2] += colors[name];
1654
+ canvas[o + 2] += $.str(name, key).padStart(key);
1655
+ if (colors?.[name]) canvas[o + 2] += $.reset;
1656
+ canvas[o + 2] += " " + m.join("").trimEnd() + (!colors ? "" : $.reset);
1657
+ canvas[o + 3] = " ".repeat(1 + key) + l.join("").trimEnd() + (!colors ? "" : $.reset);
1658
+ });
1659
+ canvas[canvas.length - 2] += " ".repeat(1 + key);
1660
+ canvas[canvas.length - 2] += symbols.bl + " ".repeat(size) + symbols.br;
1661
+ const rmin = fmt(tmin);
1662
+ const rmax = fmt(tmax);
1663
+ const rmid = fmt((tmin + tmax) / 2);
1664
+ const gap = (size - rmin.length - rmid.length - rmax.length) / 2;
1665
+ canvas[canvas.length - 1] += " ".repeat(1 + key);
1666
+ canvas[canvas.length - 1] += !colors ? rmin : $.cyan + rmin + $.reset;
1667
+ canvas[canvas.length - 1] += " ".repeat(1 + gap | 0);
1668
+ canvas[canvas.length - 1] += !colors ? rmid : $.gray + rmid + $.reset;
1669
+ canvas[canvas.length - 1] += " ".repeat(1 + Math.ceil(gap));
1670
+ canvas[canvas.length - 1] += !colors ? rmax : $.magenta + rmax + $.reset;
1671
+ return canvas;
1672
+ }
1673
+ }
1674
+ };
1675
+ //#endregion
1676
+ //#region src/runner.ts
1677
+ /**
1678
+ * Turns the `alias -> spec` map into npm alias-install specifiers
1679
+ * (`alias@npm:spec`), which let several versions of one package coexist in a
1680
+ * single `node_modules`.
1681
+ */
1682
+ function installSpecs(packages = {}) {
1683
+ return Object.entries(packages).map(([alias, spec]) => `${alias}@npm:${spec}`);
1684
+ }
1685
+ /** Resolves every declared alias to its module namespace via the resolver. */
1686
+ async function loadModules(packages = {}, resolve) {
1687
+ const modules = {};
1688
+ for (const alias of Object.keys(packages)) modules[alias] = await resolve(alias);
1689
+ return modules;
1690
+ }
1691
+ /**
1692
+ * Registers each case with the bench registrar, wrapping it so it is called
1693
+ * with the shared {@link BenchContext}. Returns the labels in registration
1694
+ * order.
1695
+ */
1696
+ function registerCases(config, modules, bench) {
1697
+ const ctx = { modules };
1698
+ return Object.entries(config.cases).map(([key, fn]) => {
1699
+ const label = `${config.name} :: ${key}`;
1700
+ bench(label, () => fn(ctx));
1701
+ return label;
1702
+ });
1703
+ }
1704
+ //#endregion
1705
+ //#region src/harness.ts
1706
+ /** Writable tmpfs the sandbox mounts; aliased package versions install here. */
1707
+ const INSTALL_DIR = "/bench";
1708
+ const benchFile = process.argv[2];
1709
+ const config = (await import(pathToFileURL(benchFile).href)).default;
1710
+ const specs = installSpecs(config.packages);
1711
+ if (specs.length > 0) {
1712
+ spawnSync("npm", ["init", "-y"], {
1713
+ cwd: INSTALL_DIR,
1714
+ stdio: "inherit"
1715
+ });
1716
+ if (spawnSync("npm", [
1717
+ "install",
1718
+ "--no-audit",
1719
+ "--no-fund",
1720
+ ...specs
1721
+ ], {
1722
+ cwd: INSTALL_DIR,
1723
+ stdio: "inherit"
1724
+ }).status !== 0) throw new Error("bench: installing benchmark packages failed");
1725
+ }
1726
+ const requireFromInstall = createRequire(`${INSTALL_DIR}/`);
1727
+ registerCases(config, await loadModules(config.packages, (alias) => import(pathToFileURL(requireFromInstall.resolve(alias)).href)), bench);
1728
+ await run();
1729
+ //#endregion
1730
+ export {};