@jonahsnider/benchmark
Version:
A Node.js benchmarking library with support for multithreading and TurboFan optimization isolation.
104 lines • 3.52 kB
JavaScript
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 {
#suites = new Map();
#multithreadedSuites = new Set();
/**
* The {@link SuiteLike}s in this {@link (Benchmark:class)}.
*/
// eslint-disable-next-line @typescript-eslint/member-ordering
suites = this.#suites;
addSuite(suiteLike, options) {
assert.ok(!this.#suites.has(suiteLike.name), new RangeError(`A suite with the name "${suiteLike.name}" already exists`));
if (options?.threaded) {
assert.ok(suiteLike.filepath);
// eslint-disable-next-line promise/prefer-await-to-then
return Thread.init(suiteLike.filepath).then(threadedSuite => {
this.#suites.set(threadedSuite.name, threadedSuite);
this.#multithreadedSuites.add(threadedSuite.name);
return this;
});
}
this.#suites.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(this.#suites.values(), suite => this.#multithreadedSuites.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;
}
}
//# sourceMappingURL=benchmark.js.map