diff --git a/examples/src/simple.ts b/examples/src/simple.ts index 71806a5..84733cb 100644 --- a/examples/src/simple.ts +++ b/examples/src/simple.ts @@ -37,19 +37,19 @@ await bench.run(); console.table( bench.tasks.map(({ name, result }) => (result ? ({ 'Task Name': name, - 'ops/sec': result.hz, - 'Average Time (ps)': result.mean * 1000, - 'Variance (ps)': result.variance * 1000, + 'ops/sec': parseInt(result.hz, 10).toLocaleString(), + 'Average Time (ns)': result.mean * 1000 * 1000, + Margin: `\xb1${result.rme.toFixed(2)}%`, Samples: result.samples.length, }) : null)), ); // Output: -// ┌─────────┬──────────────────┬────────────────────┬─────────────────────┬───────────────────────┬─────────┐ -// │ (index) │ Task Name │ ops/sec │ Average Time (ps) │ Variance (ps) │ Samples │ -// ├─────────┼──────────────────┼────────────────────┼─────────────────────┼───────────────────────┼─────────┤ -// │ 0 │ 'switch 1' │ 14463.371878123484 │ 0.06914017066190119 │ 0.006014460930767669 │ 414836 │ -// │ 1 │ 'switch 2' │ 14880.867755337544 │ 0.06720038215790977 │ 0.0004529273320957288 │ 458644 │ -// │ 2 │ 'async switch 1' │ 7970.65255150332 │ 0.1254602422497256 │ 0.010617119343817942 │ 367049 │ -// │ 3 │ 'async switch 2' │ 8702.849913513868 │ 0.11490488862127687 │ 0.002613875857497825 │ 389356 │ -// └─────────┴──────────────────┴────────────────────┴─────────────────────┴───────────────────────┴─────────┘ +// ┌─────────┬──────────────────┬──────────────┬────────────────────┬──────────┬─────────┐ +// │ (index) │ Task Name │ ops/sec │ Average Time (ns) │ Margin │ Samples │ +// ├─────────┼──────────────────┼──────────────┼────────────────────┼──────────┼─────────┤ +// │ 0 │ 'switch 1' │ '15,370,548' │ 65.05948714494949 │ '±1.89%' │ 1537055 │ +// │ 1 │ 'switch 2' │ '14,826,005' │ 67.44905232421046 │ '±2.69%' │ 1482601 │ +// │ 2 │ 'async switch 1' │ '8,196,259' │ 122.00687003709369 │ '±5.01%' │ 819626 │ +// │ 3 │ 'async switch 2' │ '7,830,281' │ 127.7093250277111 │ '±5.79%' │ 788521 │ +// └─────────┴──────────────────┴──────────────┴────────────────────┴──────────┴─────────┘ diff --git a/package.json b/package.json index 084874a..47c3e49 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,6 @@ "version": "2.3.0", "type": "module", "packageManager": "pnpm@7.5.1", - "engines": { - "node": ">= 10.0.0" - }, "scripts": { "dev": "tsup --watch", "build": "tsup", diff --git a/src/bench.ts b/src/bench.ts index ffbe4d5..366a008 100644 --- a/src/bench.ts +++ b/src/bench.ts @@ -8,7 +8,7 @@ import type { } from 'types/index'; import { createBenchEvent } from './event'; import Task from './task'; -import { now } from './utils'; +import { isAsyncFunction, isAsyncFunctionDirty, now } from './utils'; /** * The Benchmark instance for keeping track of the benchmark tasks and controlling @@ -32,6 +32,8 @@ export default class Bench extends EventTarget { now = now; + isAsyncFunction: (fn: Fn) => boolean; + setup: Hook; teardown: Hook; @@ -42,6 +44,7 @@ export default class Bench extends EventTarget { this.warmupTime = options.warmupTime ?? this.warmupTime; this.warmupIterations = options.warmupIterations ?? this.warmupIterations; this.time = options.time ?? this.time; + this.isAsyncFunction = options.dirtyAsyncCheck ? isAsyncFunctionDirty : isAsyncFunction; this.iterations = options.iterations ?? this.iterations; this.signal = options.signal; // eslint-disable-next-line @typescript-eslint/no-empty-function diff --git a/src/task.ts b/src/task.ts index 7dbe5f4..0236698 100644 --- a/src/task.ts +++ b/src/task.ts @@ -1,11 +1,10 @@ import type { Fn, TaskEvents, TaskResult, TaskEventsMap, } from 'types/index'; -import { types } from 'util'; import Bench from './bench'; import tTable from './constants'; import { createBenchEvent } from './event'; -import { getMean, getSum, getVariance } from './utils'; +import { getMean, getVariance } from './utils'; /** * A class that represents each benchmark task in Tinybench. It keeps track of the @@ -46,9 +45,9 @@ export default class Task extends EventTarget { */ async run() { this.dispatchEvent(createBenchEvent('start', this)); - const startTime = this.bench.now(); // ms let totalTime = 0; // ms const samples: number[] = []; + const isAsync = this.bench.isAsyncFunction(this.fn); await this.bench.setup(this, 'run'); while ( @@ -56,7 +55,6 @@ export default class Task extends EventTarget { && !this.bench.signal?.aborted ) { let taskStart = 0; - const isAsync = types.isAsyncFunction(this.fn); try { taskStart = this.bench.now(); @@ -68,11 +66,10 @@ export default class Task extends EventTarget { const taskTime = this.bench.now() - taskStart; this.runs += 1; samples.push(taskTime); + totalTime += taskTime; } catch (e) { this.setResult({ error: e }); } - - totalTime = this.bench.now() - startTime; } await this.bench.teardown(this, 'run'); @@ -81,8 +78,8 @@ export default class Task extends EventTarget { { const min = samples[0]!; const max = samples[samples.length - 1]!; - const period = getSum(samples) / this.runs; - const hz = 1 / period; + const period = totalTime / this.runs; + const hz = 1000 / period; // benchmark.js: https://github.com/bestiejs/benchmark.js/blob/42f3b732bac3640eddb3ae5f50e445f3141016fd/benchmark.js#L1912-L1927 const mean = getMean(samples); diff --git a/src/utils.ts b/src/utils.ts index 62c6e5b..7a7bf51 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,5 @@ +import { Fn } from 'types'; + export const nanoToMs = (nano: number) => nano / 1e6; export const now = () => { @@ -20,7 +22,23 @@ export const getVariance = (samples: number[], mean: number) => { return result / (samples.length - 1) || 0; }; +// eslint-disable-next-line @typescript-eslint/no-empty-function +const AsyncFunctionConstructor = (async () => {}).constructor; + /** - * Computes the sum of a sample + * an async function check method only consider runtime support async syntax */ -export const getSum = (samples: number[]) => samples.reduce((sum, n) => sum + n, 0); +export const isAsyncFunction = (fn: Fn) => fn.constructor === AsyncFunctionConstructor; + +/** + * an async function check method consider runtime not support async syntax + */ +export const isAsyncFunctionDirty = (fn: Fn) => { + try { + const ret = fn(); + return !!ret && typeof ret === 'object' && typeof ret.then === 'function'; + } catch { + // if fn throw error directly, consider it's a sync function + return false; + } +}; diff --git a/test/index.test.ts b/test/index.test.ts index 346406c..dc99cb3 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -23,7 +23,8 @@ test('basic', async () => { expect(tasks[1]!.name).toEqual('bar'); expect(tasks[1]!.result!.totalTime).toBeGreaterThan(100); - expect(tasks[0]!.result!.hz * tasks[0]!.result!.period).toBeCloseTo(1); + // hz is ops/sec, period is ms unit value + expect(tasks[0]!.result!.hz * tasks[0]!.result!.period).toBeCloseTo(1000); }); test('runs should be equal-more than time and iterations', async () => { @@ -48,7 +49,9 @@ test('events order', async () => { warmupTime: 0, }); bench + // eslint-disable-next-line @typescript-eslint/no-empty-function .add('foo', async () => {}) + // eslint-disable-next-line @typescript-eslint/no-empty-function .add('bar', async () => {}) .add('error', async () => { throw new Error('fake'); diff --git a/types/index.d.ts b/types/index.d.ts index 0e76155..a0b8f78 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -179,6 +179,16 @@ export type Options = { */ now?: () => number; + /** + * decide use which way to check task's fn is a async function. + * + * when it's false, only compare function's constructor + * when is's true, run task's fn and check whether return a PromiseLike object + * + * @default false + */ + dirtyAsyncCheck?: boolean; + /** * An AbortSignal for aborting the benchmark */