@jonahsnider/benchmark
Version:
A Node.js benchmarking library with support for multithreading and TurboFan optimization isolation.
108 lines • 4.01 kB
JavaScript
var _Benchmark_suites, _Benchmark_multithreadedSuites;
import { __classPrivateFieldGet } from "tslib";
import assert from 'node:assert/strict';
import { partition } from '@jonahsnider/util';
import { Thread } from './thread.js';
/**
* A benchmark which has many {@link SuiteLike}s.
*
* @example
* ```js
* import { Benchmark, Suite } from '@jonahsnider/benchmark';
*
* const benchmark = new Benchmark();
*
* const suite = new Suite('concatenation', { warmup: { durationMs: 10_000 }, run: { durationMs: 10_000 } })
* .addTest('+', () => 'a' + 'b')
* .addTest('templates', () => `${'a'}${'b'}`)
* .addTest('.concat()', () => 'a'.concat('b'));
*
* benchmark.addSuite(suite);
*
* const results = await benchmark.run();
*
* console.log(results);
* ```
*
* @public
*/
export class Benchmark {
constructor() {
_Benchmark_suites.set(this, new Map());
_Benchmark_multithreadedSuites.set(this, new Set());
/**
* The {@link SuiteLike}s in this {@link (Benchmark:class)}.
*/
// eslint-disable-next-line @typescript-eslint/member-ordering
this.suites = __classPrivateFieldGet(this, _Benchmark_suites, "f");
}
addSuite(suiteLike, options) {
assert.ok(!__classPrivateFieldGet(this, _Benchmark_suites, "f").has(suiteLike.name), new RangeError(`A suite with the name "${suiteLike.name}" already exists`));
if (options?.threaded) {
assert.ok(suiteLike.filepath);
return Thread.init(suiteLike.filepath).then(threadedSuite => {
__classPrivateFieldGet(this, _Benchmark_suites, "f").set(threadedSuite.name, threadedSuite);
__classPrivateFieldGet(this, _Benchmark_multithreadedSuites, "f").add(threadedSuite.name);
return this;
});
}
__classPrivateFieldGet(this, _Benchmark_suites, "f").set(suiteLike.name, suiteLike);
return this;
}
/**
* Run all {@link (Suite:class)}s for this {@link (Benchmark:class)}.
*
* @example
* ```js
* const results = await benchmark.runSuites();
* ```
*
* @example
* Using an `AbortSignal` to cancel the benchmark:
* ```js
* const ac = new AbortController();
* const signal = ac.signal;
*
* benchmark
* .runSuites(signal)
* .then(console.log)
* .catch(error => {
* if (error.name === 'AbortError') {
* console.log('The benchmark was aborted');
* }
* });
*
* ac.abort();
* ```
*
* @param abortSignal - An optional `AbortSignal` that can be used to cancel the running suites
*
* @returns A {@link (Benchmark:namespace).Results} `Map`
*/
async runSuites(abortSignal, options) {
const results = new Map();
const [multithreaded, singleThreaded] = partition(__classPrivateFieldGet(this, _Benchmark_suites, "f").values(), suite => __classPrivateFieldGet(this, _Benchmark_multithreadedSuites, "f").has(suite.name));
// Single-threaded suites are executed serially to avoid any interference
for (const suite of singleThreaded) {
// eslint-disable-next-line no-await-in-loop
const suiteResults = await suite.run(abortSignal);
results.set(suite.name, suiteResults);
}
if (options?.sequential) {
for (const suite of multithreaded) {
// eslint-disable-next-line no-await-in-loop
const suiteResults = await suite.run(abortSignal);
results.set(suite.name, suiteResults);
}
}
else {
await Promise.all(multithreaded.map(async (suite) => {
const suiteResults = await suite.run(abortSignal);
results.set(suite.name, suiteResults);
}));
}
return results;
}
}
_Benchmark_suites = new WeakMap(), _Benchmark_multithreadedSuites = new WeakMap();
//# sourceMappingURL=benchmark.js.map