mirror of
https://github.com/denoland/deno.git
synced 2025-10-02 23:24:37 +00:00
Return results in benchmark promise (#5842)
This commit is contained in:
parent
fe7d6824c9
commit
6de59f1908
4 changed files with 199 additions and 53 deletions
|
@ -179,10 +179,6 @@ Runs all registered benchmarks serially. Filtering can be applied by setting
|
||||||
`BenchmarkRunOptions.only` and/or `BenchmarkRunOptions.skip` to regular
|
`BenchmarkRunOptions.only` and/or `BenchmarkRunOptions.skip` to regular
|
||||||
expressions matching benchmark names.
|
expressions matching benchmark names.
|
||||||
|
|
||||||
##### `runIfMain(meta: ImportMeta, opts?: BenchmarkRunOptions): Promise<void>`
|
|
||||||
|
|
||||||
Runs specified benchmarks if the enclosing script is main.
|
|
||||||
|
|
||||||
##### Other exports
|
##### Other exports
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
const { exit, noColor } = Deno;
|
const { noColor } = Deno;
|
||||||
|
|
||||||
interface BenchmarkClock {
|
interface BenchmarkClock {
|
||||||
start: number;
|
start: number;
|
||||||
|
@ -30,6 +30,30 @@ export interface BenchmarkDefinition {
|
||||||
export interface BenchmarkRunOptions {
|
export interface BenchmarkRunOptions {
|
||||||
only?: RegExp;
|
only?: RegExp;
|
||||||
skip?: RegExp;
|
skip?: RegExp;
|
||||||
|
silent?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BenchmarkResult {
|
||||||
|
name: string;
|
||||||
|
totalMs: number;
|
||||||
|
runsCount?: number;
|
||||||
|
runsAvgMs?: number;
|
||||||
|
runsMs?: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BenchmarkRunResult {
|
||||||
|
measured: number;
|
||||||
|
filtered: number;
|
||||||
|
results: BenchmarkResult[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BenchmarkRunError extends Error {
|
||||||
|
benchmarkName?: string;
|
||||||
|
constructor(msg: string, benchmarkName?: string) {
|
||||||
|
super(msg);
|
||||||
|
this.name = "BenchmarkRunError";
|
||||||
|
this.benchmarkName = benchmarkName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function red(text: string): string {
|
function red(text: string): string {
|
||||||
|
@ -44,16 +68,22 @@ function verifyOr1Run(runs?: number): number {
|
||||||
return runs && runs >= 1 && runs !== Infinity ? Math.floor(runs) : 1;
|
return runs && runs >= 1 && runs !== Infinity ? Math.floor(runs) : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertTiming(clock: BenchmarkClock): void {
|
function assertTiming(clock: BenchmarkClock, benchmarkName: string): void {
|
||||||
// NaN indicates that a benchmark has not been timed properly
|
// NaN indicates that a benchmark has not been timed properly
|
||||||
if (!clock.stop) {
|
if (!clock.stop) {
|
||||||
throw new Error("The benchmark timer's stop method must be called");
|
throw new BenchmarkRunError(
|
||||||
|
`Running benchmarks FAILED during benchmark named [${benchmarkName}]. The benchmark timer's stop method must be called`,
|
||||||
|
benchmarkName
|
||||||
|
);
|
||||||
} else if (!clock.start) {
|
} else if (!clock.start) {
|
||||||
throw new Error("The benchmark timer's start method must be called");
|
throw new BenchmarkRunError(
|
||||||
|
`Running benchmarks FAILED during benchmark named [${benchmarkName}]. The benchmark timer's start method must be called`,
|
||||||
|
benchmarkName
|
||||||
|
);
|
||||||
} else if (clock.start > clock.stop) {
|
} else if (clock.start > clock.stop) {
|
||||||
throw new Error(
|
throw new BenchmarkRunError(
|
||||||
"The benchmark timer's start method must be called before its " +
|
`Running benchmarks FAILED during benchmark named [${benchmarkName}]. The benchmark timer's start method must be called before its stop method`,
|
||||||
"stop method"
|
benchmarkName
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,7 +123,8 @@ export function bench(
|
||||||
export async function runBenchmarks({
|
export async function runBenchmarks({
|
||||||
only = /[^\s]/,
|
only = /[^\s]/,
|
||||||
skip = /^\s*$/,
|
skip = /^\s*$/,
|
||||||
}: BenchmarkRunOptions = {}): Promise<void> {
|
silent,
|
||||||
|
}: BenchmarkRunOptions = {}): Promise<BenchmarkRunResult> {
|
||||||
// Filtering candidates by the "only" and "skip" constraint
|
// Filtering candidates by the "only" and "skip" constraint
|
||||||
const benchmarks: BenchmarkDefinition[] = candidates.filter(
|
const benchmarks: BenchmarkDefinition[] = candidates.filter(
|
||||||
({ name }): boolean => only.test(name) && !skip.test(name)
|
({ name }): boolean => only.test(name) && !skip.test(name)
|
||||||
|
@ -101,19 +132,28 @@ export async function runBenchmarks({
|
||||||
// Init main counters and error flag
|
// Init main counters and error flag
|
||||||
const filtered = candidates.length - benchmarks.length;
|
const filtered = candidates.length - benchmarks.length;
|
||||||
let measured = 0;
|
let measured = 0;
|
||||||
let failed = false;
|
let failError: Error | undefined = undefined;
|
||||||
// Setting up a shared benchmark clock and timer
|
// Setting up a shared benchmark clock and timer
|
||||||
const clock: BenchmarkClock = { start: NaN, stop: NaN };
|
const clock: BenchmarkClock = { start: NaN, stop: NaN };
|
||||||
const b = createBenchmarkTimer(clock);
|
const b = createBenchmarkTimer(clock);
|
||||||
|
|
||||||
|
if (!silent) {
|
||||||
// Iterating given benchmark definitions (await-in-loop)
|
// Iterating given benchmark definitions (await-in-loop)
|
||||||
console.log(
|
console.log(
|
||||||
"running",
|
"running",
|
||||||
benchmarks.length,
|
benchmarks.length,
|
||||||
`benchmark${benchmarks.length === 1 ? " ..." : "s ..."}`
|
`benchmark${benchmarks.length === 1 ? " ..." : "s ..."}`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializing results array
|
||||||
|
const benchmarkResults: BenchmarkResult[] = [];
|
||||||
for (const { name, runs = 0, func } of benchmarks) {
|
for (const { name, runs = 0, func } of benchmarks) {
|
||||||
|
if (!silent) {
|
||||||
// See https://github.com/denoland/deno/pull/1452 about groupCollapsed
|
// See https://github.com/denoland/deno/pull/1452 about groupCollapsed
|
||||||
console.groupCollapsed(`benchmark ${name} ... `);
|
console.groupCollapsed(`benchmark ${name} ... `);
|
||||||
|
}
|
||||||
|
|
||||||
// Trying benchmark.func
|
// Trying benchmark.func
|
||||||
let result = "";
|
let result = "";
|
||||||
try {
|
try {
|
||||||
|
@ -121,60 +161,83 @@ export async function runBenchmarks({
|
||||||
// b is a benchmark timer interfacing an unset (NaN) benchmark clock
|
// b is a benchmark timer interfacing an unset (NaN) benchmark clock
|
||||||
await func(b);
|
await func(b);
|
||||||
// Making sure the benchmark was started/stopped properly
|
// Making sure the benchmark was started/stopped properly
|
||||||
assertTiming(clock);
|
assertTiming(clock, name);
|
||||||
result = `${clock.stop - clock.start}ms`;
|
result = `${clock.stop - clock.start}ms`;
|
||||||
|
// Adding one-time run to results
|
||||||
|
benchmarkResults.push({ name, totalMs: clock.stop - clock.start });
|
||||||
} else if (runs > 1) {
|
} else if (runs > 1) {
|
||||||
// Averaging runs
|
// Averaging runs
|
||||||
let pendingRuns = runs;
|
let pendingRuns = runs;
|
||||||
let totalMs = 0;
|
let totalMs = 0;
|
||||||
|
// Initializing array holding individual runs ms
|
||||||
|
const runsMs = [];
|
||||||
// Would be better 2 not run these serially
|
// Would be better 2 not run these serially
|
||||||
while (true) {
|
while (true) {
|
||||||
// b is a benchmark timer interfacing an unset (NaN) benchmark clock
|
// b is a benchmark timer interfacing an unset (NaN) benchmark clock
|
||||||
await func(b);
|
await func(b);
|
||||||
// Making sure the benchmark was started/stopped properly
|
// Making sure the benchmark was started/stopped properly
|
||||||
assertTiming(clock);
|
assertTiming(clock, name);
|
||||||
// Summing up
|
// Summing up
|
||||||
totalMs += clock.stop - clock.start;
|
totalMs += clock.stop - clock.start;
|
||||||
|
// Adding partial result
|
||||||
|
runsMs.push(clock.stop - clock.start);
|
||||||
// Resetting the benchmark clock
|
// Resetting the benchmark clock
|
||||||
clock.start = clock.stop = NaN;
|
clock.start = clock.stop = NaN;
|
||||||
// Once all ran
|
// Once all ran
|
||||||
if (!--pendingRuns) {
|
if (!--pendingRuns) {
|
||||||
result = `${runs} runs avg: ${totalMs / runs}ms`;
|
result = `${runs} runs avg: ${totalMs / runs}ms`;
|
||||||
|
// Adding result of multiple runs
|
||||||
|
benchmarkResults.push({
|
||||||
|
name,
|
||||||
|
totalMs,
|
||||||
|
runsCount: runs,
|
||||||
|
runsAvgMs: totalMs / runs,
|
||||||
|
runsMs,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
failed = true;
|
failError = err;
|
||||||
|
|
||||||
|
if (!silent) {
|
||||||
console.groupEnd();
|
console.groupEnd();
|
||||||
console.error(red(err.stack));
|
console.error(red(err.stack));
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!silent) {
|
||||||
// Reporting
|
// Reporting
|
||||||
console.log(blue(result));
|
console.log(blue(result));
|
||||||
console.groupEnd();
|
console.groupEnd();
|
||||||
|
}
|
||||||
|
|
||||||
measured++;
|
measured++;
|
||||||
// Resetting the benchmark clock
|
// Resetting the benchmark clock
|
||||||
clock.start = clock.stop = NaN;
|
clock.start = clock.stop = NaN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!silent) {
|
||||||
// Closing results
|
// Closing results
|
||||||
console.log(
|
console.log(
|
||||||
`benchmark result: ${failed ? red("FAIL") : blue("DONE")}. ` +
|
`benchmark result: ${!!failError ? red("FAIL") : blue("DONE")}. ` +
|
||||||
`${measured} measured; ${filtered} filtered`
|
`${measured} measured; ${filtered} filtered`
|
||||||
);
|
);
|
||||||
// Making sure the program exit code is not zero in case of failure
|
|
||||||
if (failed) {
|
|
||||||
setTimeout((): void => exit(1), 0);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** Runs specified benchmarks if the enclosing script is main. */
|
// Making sure the program exit code is not zero in case of failure
|
||||||
export function runIfMain(
|
if (!!failError) {
|
||||||
meta: ImportMeta,
|
throw failError;
|
||||||
opts: BenchmarkRunOptions = {}
|
|
||||||
): Promise<void> {
|
|
||||||
if (meta.main) {
|
|
||||||
return runBenchmarks(opts);
|
|
||||||
}
|
}
|
||||||
return Promise.resolve(undefined);
|
|
||||||
|
const benchmarkRunResult = {
|
||||||
|
measured,
|
||||||
|
filtered,
|
||||||
|
results: benchmarkResults,
|
||||||
|
};
|
||||||
|
|
||||||
|
return benchmarkRunResult;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// https://deno.land/std/testing/bench.ts
|
// https://deno.land/std/testing/bench.ts
|
||||||
import { BenchmarkTimer, bench, runIfMain } from "./bench.ts";
|
import { BenchmarkTimer, bench, runBenchmarks } from "./bench.ts";
|
||||||
|
|
||||||
// Basic
|
// Basic
|
||||||
bench(function forIncrementX1e9(b: BenchmarkTimer): void {
|
bench(function forIncrementX1e9(b: BenchmarkTimer): void {
|
||||||
|
@ -26,4 +26,6 @@ bench(function throwing(b): void {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bench control
|
// Bench control
|
||||||
runIfMain(import.meta, { skip: /throw/ });
|
if (import.meta.main) {
|
||||||
|
runBenchmarks({ skip: /throw/ });
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
const { test } = Deno;
|
const { test } = Deno;
|
||||||
import { bench, runBenchmarks } from "./bench.ts";
|
import { bench, runBenchmarks, BenchmarkRunError } from "./bench.ts";
|
||||||
|
import {
|
||||||
import "./bench_example.ts";
|
assertEquals,
|
||||||
|
assert,
|
||||||
|
assertThrows,
|
||||||
|
assertThrowsAsync,
|
||||||
|
} from "./asserts.ts";
|
||||||
|
|
||||||
test({
|
test({
|
||||||
name: "benching",
|
name: "benching",
|
||||||
|
@ -57,6 +61,87 @@ test({
|
||||||
// Throws bc the timer's stop method is never called
|
// Throws bc the timer's stop method is never called
|
||||||
});
|
});
|
||||||
|
|
||||||
await runBenchmarks({ skip: /throw/ });
|
const benchResult = await runBenchmarks({ skip: /throw/ });
|
||||||
|
|
||||||
|
assertEquals(benchResult.measured, 5);
|
||||||
|
assertEquals(benchResult.filtered, 1);
|
||||||
|
assertEquals(benchResult.results.length, 5);
|
||||||
|
|
||||||
|
const resultWithMultipleRunsFiltered = benchResult.results.filter(
|
||||||
|
(r) => r.name === "runs100ForIncrementX1e6"
|
||||||
|
);
|
||||||
|
assertEquals(resultWithMultipleRunsFiltered.length, 1);
|
||||||
|
|
||||||
|
const resultWithMultipleRuns = resultWithMultipleRunsFiltered[0];
|
||||||
|
assert(!!resultWithMultipleRuns.runsCount);
|
||||||
|
assert(!!resultWithMultipleRuns.runsAvgMs);
|
||||||
|
assert(!!resultWithMultipleRuns.runsMs);
|
||||||
|
assertEquals(resultWithMultipleRuns.runsCount, 100);
|
||||||
|
assertEquals(resultWithMultipleRuns.runsMs!.length, 100);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "benchWithoutName",
|
||||||
|
fn() {
|
||||||
|
assertThrows(
|
||||||
|
(): void => {
|
||||||
|
bench(() => {});
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
"The benchmark function must not be anonymous"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "benchWithoutStop",
|
||||||
|
fn: async function (): Promise<void> {
|
||||||
|
await assertThrowsAsync(
|
||||||
|
async (): Promise<void> => {
|
||||||
|
bench(function benchWithoutStop(b): void {
|
||||||
|
b.start();
|
||||||
|
// Throws bc the timer's stop method is never called
|
||||||
|
});
|
||||||
|
await runBenchmarks({ only: /benchWithoutStop/, silent: true });
|
||||||
|
},
|
||||||
|
BenchmarkRunError,
|
||||||
|
"The benchmark timer's stop method must be called"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "benchWithoutStart",
|
||||||
|
fn: async function (): Promise<void> {
|
||||||
|
await assertThrowsAsync(
|
||||||
|
async (): Promise<void> => {
|
||||||
|
bench(function benchWithoutStart(b): void {
|
||||||
|
b.stop();
|
||||||
|
// Throws bc the timer's start method is never called
|
||||||
|
});
|
||||||
|
await runBenchmarks({ only: /benchWithoutStart/, silent: true });
|
||||||
|
},
|
||||||
|
BenchmarkRunError,
|
||||||
|
"The benchmark timer's start method must be called"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "benchStopBeforeStart",
|
||||||
|
fn: async function (): Promise<void> {
|
||||||
|
await assertThrowsAsync(
|
||||||
|
async (): Promise<void> => {
|
||||||
|
bench(function benchStopBeforeStart(b): void {
|
||||||
|
b.stop();
|
||||||
|
b.start();
|
||||||
|
// Throws bc the timer's stop is called before start
|
||||||
|
});
|
||||||
|
await runBenchmarks({ only: /benchStopBeforeStart/, silent: true });
|
||||||
|
},
|
||||||
|
BenchmarkRunError,
|
||||||
|
"The benchmark timer's start method must be called before its stop method"
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue