@jonahsnider/benchmark
Version:
A Node.js benchmarking library with support for multithreading and TurboFan optimization isolation.
391 lines (381 loc) • 10.4 kB
TypeScript
/// <reference types="node" />
import type { RecordableHistogram } from 'node:perf_hooks';
/**
* @public
*/
export declare namespace Benchmark {
/**
* A `Map` where keys are the {@link (Suite:namespace).Name | suite names} and values are the {@link (Suite:namespace).Results | suite results}.
*
* @public
*/
export type Results = Map<Suite.Name, Suite.Results>;
/**
* Options for running a {@link (Benchmark:class)}.
*
* @public
*/
export type RunOptions = {
/**
* Whether to run multithreaded suites sequentially or in parallel.
*
* @public
*/
sequential?: boolean | undefined;
};
}
/**
* 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 declare class Benchmark {
#private;
/**
* The {@link SuiteLike}s in this {@link (Benchmark:class)}.
*/
readonly suites: ReadonlyMap<Suite.Name, SuiteLike>;
/**
* Add a {@link SuiteLike} to this {@link (Benchmark:class)}.
*
* @example
* ```js
* benchmark.addSuite(suite);
* ```
*
* @param suite - The {@link SuiteLike} to add
*
* @returns `this`
*/
addSuite(suite: SuiteLike, options?: undefined | {
threaded: false;
}): this;
/**
* Add a {@link SuiteLike} to this {@link (Benchmark:class)} by loading it in a separate thread via its filepath.
*
* @example
* ```js
* await benchmark.addSuite(suite);
* ```
*
* @param suite - A {@link (Suite:class)} with a filepath provided
*
* @returns `this`
*/
addSuite(suite: SuiteLike, options: {
threaded: true;
}): Promise<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`
*/
runSuites(abortSignal?: AbortSignal | undefined, options?: Benchmark.RunOptions): Promise<Benchmark.Results>;
}
/**
* @public
*/
export declare namespace Suite {
/**
* The name of a {@link (Suite:class)}.
*
* @public
*/
export type Name = string;
/**
* Results from running a {@link (Suite:class)}.
*
* @public
*/
export type Results = Map<Test.Name, RecordableHistogram>;
/**
* Options for running {@link (Test:class)}s.
*
* @public
*/
export type RunOptions = {
trials: number;
durationMs?: undefined;
} | {
trials?: undefined;
durationMs: number;
};
/**
* Options for running a {@link (Suite:class)}.
*
* @public
*/
export type Options = {
run: RunOptions;
warmup: RunOptions;
filepath?: string | undefined;
};
}
/**
* A collection of {@link (Test:class)}s that are different implementations of the same thing (ex. different ways of sorting an array).
*
* @example
* ```js
* import { Suite } from '@jonahsnider/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'));
*
* const results = await suite.run();
*
* console.log(results);
* ```
*
* @public
*/
export declare class Suite implements SuiteLike {
#private;
readonly name: Suite.Name;
/**
* Options for running this {@link (Suite:class)} and its warmup.
*/
readonly options: Suite.Options;
/**
* The tests in this {@link (Suite:class)}.
*/
tests: ReadonlyMap<Test.Name, Test>;
/**
* This {@link (Suite:class)}'s filepath, if it was provided.
* Used for running the {@link (Suite:class)} in a separate thread.
*/
get filepath(): string | undefined;
/**
* Creates a new {@link (Suite:class)}.
*
* @example
* ```js
* import { Suite } from '@jonahsnider/benchmark';
*
* const suite = new Suite('concatenation', { warmup: { durationMs: 10_000 }, run: { durationMs: 10_000 } });
* ```
*
* @example
* Suites that specify a filepath can be run in a separate thread in a {@link (Benchmark:class)}.
* ```js
* import { Suite } from '@jonahsnider/benchmark';
*
* const suite = new Suite('concatenation', {
* warmup: { durationMs: 10_000 },
* run: { durationMs: 10_000 },
* filepath: import.meta.url
* });
* ```
*
* @param name - The name of the {@link (Suite:class)}
* @param options - Options for the {@link (Suite:class)}
*/
constructor(name: Suite.Name,
/**
* Options for running this {@link (Suite:class)} and its warmup.
*/
options: Suite.Options);
/**
* Adds a test to this {@link (Suite:class)}.
*
* @example
* ```js
* const test = new Test(() => 'a' + 'b');
*
* suite.addTest('+', test);
* ```
*
* @param testName - The name of the test
* @param test - The test to add
*
* @returns `this`
*/
addTest(testName: string, test: Test): this;
/**
* Creates and adds a test to this {@link (Suite:class)}.
*
* @example
* ```js
* suite.addTest('+', () => 'a' + 'b');
* ```
*
* @param testName - The name of the test
* @param fn - The function to run
*
* @returns `this`
*/
addTest(testName: string, fn: () => unknown): this;
/**
* Runs this {@link (Suite:class)} using {@link (Suite:class).options}.
*
* @example
* ```js
* const results = await suite.run();
* ```
*
* @example
* Using an `AbortSignal` to cancel the suite:
* ```js
* const ac = new AbortController();
* const signal = ac.signal;
*
* suite
* .run(signal)
* .then(console.log)
* .catch(error => {
* if (error.name === 'AbortError') {
* console.log('The suite was aborted');
* }
* });
*
* ac.abort();
* ```
*
* @returns The results of running this {@link (Suite:class)}
*/
run(abortSignal?: AbortSignal | undefined): Promise<Suite.Results>;
}
/**
* A suite of related tests that can be run together.
*
* @public
*/
export declare type SuiteLike = {
/**
* The name of this {@link SuiteLike}.
*/
readonly name: Suite.Name;
/**
* The filepath to this {@link SuiteLike}, when available.
*/
readonly filepath?: string | undefined;
/**
* Runs this {@link SuiteLike}.
*
* @example
* A synchronous implementation:
* ```js
* const results = suite.run();
* ```
*
* @example
* An asynchronous implementation:
* ```js
* const results = await suite.run();
* ```
*
* @example
* Using an `AbortSignal` to cancel the suite:
* ```js
* const ac = new AbortController();
* const signal = ac.signal;
*
* suite
* .run(signal)
* .then(console.log)
* .catch(error => {
* if (error.name === 'AbortError') {
* console.log('The suite was aborted');
* }
* });
*
* ac.abort();
* ```
*
* @returns The results of running this {@link SuiteLike}
*/
run(abortSignal?: AbortSignal | undefined): Suite.Results | PromiseLike<Suite.Results>;
};
/**
* @public
*/
export declare namespace Test {
/**
* The name of a {@link (Test:class)}.
*
* @public
*/
export type Name = string;
}
/**
* Tracks a function's execution in {@link (Test:class).histogram}.
*
* @example
* ```ts
* import { Test } from '@jonahsnider/benchmark';
*
* const test = new Test(() => 'a' + 'b');
* ```
*
* @public
*/
export declare class Test<T = unknown> {
#private;
/** Execution times for this {@link (Test:class)}'s implementation. */
readonly histogram: RecordableHistogram;
/**
* Create a new {@link (Test:class)} with a given implementation.
*
* You probably don't want to instantiate this class directly, instead you can register tests with {@link (Suite:class).(addTest:2)}.
* You can also register {@link (Test:class)} instances with {@link (Suite:class).(addTest:1)}.
*
* @example
* ```ts
* const test = new Test(() => 'a' + 'b');
* ```
*
* @param implementation - The implementation function of the test
*/
constructor(implementation: () => T | PromiseLike<T>);
/**
* Runs this {@link (Test:class)}'s implementation once and records the execution time in {@link (Test:class).histogram}.
*
* @returns The return value of this {@link (Test:class)}'s implementation
*/
run(): Promise<T>;
}
export { }