UNPKG

@jonahsnider/benchmark

Version:

A Node.js benchmarking library with support for multithreading and TurboFan optimization isolation.

391 lines (381 loc) 10.4 kB
/// <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 { }