mirror of
https://github.com/denoland/std.git
synced 2024-11-22 04:59:05 +00:00
Add benching (#185)
This commit is contained in:
parent
d895c60a51
commit
0a160c3925
29
benching/example.ts
Normal file
29
benching/example.ts
Normal file
@ -0,0 +1,29 @@
|
||||
// https://deno.land/x/benching/mod.ts
|
||||
import { BenchmarkTimer, runBenchmarks, bench } from "mod.ts";
|
||||
|
||||
// Simple
|
||||
bench(function forIncrementX1e9(b: BenchmarkTimer) {
|
||||
b.start();
|
||||
for (let i = 0; i < 1e9; i++);
|
||||
b.stop();
|
||||
});
|
||||
|
||||
// Reporting average measured time for $runs runs of func
|
||||
bench({
|
||||
name: "runs100ForIncrementX1e6",
|
||||
runs: 100,
|
||||
func(b: BenchmarkTimer) {
|
||||
b.start();
|
||||
for (let i: number = 0; i < 1e6; i++);
|
||||
b.stop();
|
||||
}
|
||||
});
|
||||
|
||||
// Itsabug
|
||||
bench(function throwing(b) {
|
||||
b.start();
|
||||
// Throws bc the timer's stop method is never called
|
||||
});
|
||||
|
||||
// Bench control
|
||||
runBenchmarks({ skip: /throw/ });
|
169
benching/mod.ts
Normal file
169
benching/mod.ts
Normal file
@ -0,0 +1,169 @@
|
||||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
import { exit, noColor } from "deno";
|
||||
|
||||
interface BenchmarkClock {
|
||||
start: number;
|
||||
stop: number;
|
||||
}
|
||||
|
||||
/** Provides methods for starting and stopping a benchmark clock. */
|
||||
export interface BenchmarkTimer {
|
||||
start: () => void;
|
||||
stop: () => void;
|
||||
}
|
||||
|
||||
/** Defines a benchmark through a named function. */
|
||||
export type BenchmarkFunction = {
|
||||
(b: BenchmarkTimer): void | Promise<void>;
|
||||
name: string;
|
||||
};
|
||||
|
||||
/** Defines a benchmark definition with configurable runs. */
|
||||
export interface BenchmarkDefinition {
|
||||
func: BenchmarkFunction;
|
||||
name: string;
|
||||
runs?: number;
|
||||
}
|
||||
|
||||
/** Defines runBenchmark's run constraints by matching benchmark names. */
|
||||
export interface BenchmarkRunOptions {
|
||||
only?: RegExp;
|
||||
skip?: RegExp;
|
||||
}
|
||||
|
||||
function red(text: string): string {
|
||||
return noColor ? text : `\x1b[31m${text}\x1b[0m`;
|
||||
}
|
||||
|
||||
function blue(text: string): string {
|
||||
return noColor ? text : `\x1b[34m${text}\x1b[0m`;
|
||||
}
|
||||
|
||||
function verifyOr1Run(runs?: number): number {
|
||||
return runs && runs >= 1 && runs !== Infinity ? Math.floor(runs) : 1;
|
||||
}
|
||||
|
||||
function assertTiming(clock: BenchmarkClock): void {
|
||||
// NaN indicates that a benchmark has not been timed properly
|
||||
if (!clock.stop) {
|
||||
throw new Error("The benchmark timer's stop method must be called");
|
||||
} else if (!clock.start) {
|
||||
throw new Error("The benchmark timer's start method must be called");
|
||||
} else if (clock.start > clock.stop) {
|
||||
throw new Error(
|
||||
"The benchmark timer's start method must be called before its " +
|
||||
"stop method"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function createBenchmarkTimer(clock: BenchmarkClock): BenchmarkTimer {
|
||||
return {
|
||||
start(): void {
|
||||
clock.start = Date.now();
|
||||
},
|
||||
stop(): void {
|
||||
clock.stop = Date.now();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const candidates: Array<BenchmarkDefinition> = [];
|
||||
|
||||
/** Registers a benchmark as a candidate for the runBenchmarks executor. */
|
||||
export function bench(
|
||||
benchmark: BenchmarkDefinition | BenchmarkFunction
|
||||
): void {
|
||||
if (!benchmark.name) {
|
||||
throw new Error("The benchmark function must not be anonymous");
|
||||
}
|
||||
if (typeof benchmark === "function") {
|
||||
candidates.push({ name: benchmark.name, runs: 1, func: benchmark });
|
||||
} else {
|
||||
candidates.push({
|
||||
name: benchmark.name,
|
||||
runs: verifyOr1Run(benchmark.runs),
|
||||
func: benchmark.func
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Runs all registered and non-skipped benchmarks serially. */
|
||||
export async function runBenchmarks({
|
||||
only = /[^\s]+/,
|
||||
skip = /^\s*$/
|
||||
}: BenchmarkRunOptions): Promise<void> {
|
||||
// Filtering candidates by the "only" and "skip" constraint
|
||||
const benchmarks: Array<BenchmarkDefinition> = candidates.filter(
|
||||
({ name }) => only.test(name) && !skip.test(name)
|
||||
);
|
||||
// Init main counters and error flag
|
||||
const filtered: number = candidates.length - benchmarks.length;
|
||||
let measured: number = 0;
|
||||
let failed: boolean = false;
|
||||
// Setting up a shared benchmark clock and timer
|
||||
const clock: BenchmarkClock = { start: NaN, stop: NaN };
|
||||
const b: BenchmarkTimer = createBenchmarkTimer(clock);
|
||||
// Iterating given benchmark definitions (await-in-loop)
|
||||
console.log(
|
||||
"running",
|
||||
benchmarks.length,
|
||||
`benchmark${benchmarks.length === 1 ? " ..." : "s ..."}`
|
||||
);
|
||||
for (const { name, runs, func } of benchmarks) {
|
||||
// See https://github.com/denoland/deno/pull/1452 about groupCollapsed
|
||||
console.groupCollapsed(`benchmark ${name} ... `);
|
||||
// Trying benchmark.func
|
||||
let result: string;
|
||||
try {
|
||||
if (runs === 1) {
|
||||
// b is a benchmark timer interfacing an unset (NaN) benchmark clock
|
||||
await func(b);
|
||||
// Making sure the benchmark was started/stopped properly
|
||||
assertTiming(clock);
|
||||
result = `${clock.stop - clock.start}ms`;
|
||||
} else if (runs > 1) {
|
||||
// Averaging runs
|
||||
let pendingRuns = runs;
|
||||
let totalMs: number = 0;
|
||||
// Would be better 2 not run these serially
|
||||
while (true) {
|
||||
// b is a benchmark timer interfacing an unset (NaN) benchmark clock
|
||||
await func(b);
|
||||
// Making sure the benchmark was started/stopped properly
|
||||
assertTiming(clock);
|
||||
// Summing up
|
||||
totalMs += clock.stop - clock.start;
|
||||
// Resetting the benchmark clock
|
||||
clock.start = clock.stop = NaN;
|
||||
// Once all ran
|
||||
if (!--pendingRuns) {
|
||||
result = `${runs} runs avg: ${totalMs / runs}ms`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
failed = true;
|
||||
console.groupEnd();
|
||||
console.error(red(err.stack));
|
||||
break;
|
||||
}
|
||||
// Reporting
|
||||
console.log(blue(result));
|
||||
console.groupEnd();
|
||||
measured++;
|
||||
// Resetting the benchmark clock
|
||||
clock.start = clock.stop = NaN;
|
||||
}
|
||||
// Closing results
|
||||
console.log(
|
||||
`benchmark result: ${failed ? red("FAIL") : blue("DONE")}. ` +
|
||||
`${measured} measured; ${filtered} filtered`
|
||||
);
|
||||
// Making sure the program exit code is not zero in case of failure
|
||||
if (failed) {
|
||||
setTimeout(() => exit(1), 0);
|
||||
}
|
||||
}
|
86
benching/readme.md
Normal file
86
benching/readme.md
Normal file
@ -0,0 +1,86 @@
|
||||
# benching
|
||||
|
||||
Basic benchmarking module. Provides flintstone millisecond resolution.
|
||||
|
||||
## Import
|
||||
|
||||
```ts
|
||||
import * as benching from "https://deno.land/x/benching/mod.ts";
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import {
|
||||
BenchmarkTimer,
|
||||
runBenchmarks,
|
||||
bench
|
||||
} from "https://deno.land/x/benching/mod.ts";
|
||||
|
||||
// Simple
|
||||
bench(function forIncrementX1e9(b: BenchmarkTimer) {
|
||||
b.start();
|
||||
for (let i = 0; i < 1e9; i++);
|
||||
b.stop();
|
||||
});
|
||||
|
||||
// Reporting average measured time for $runs runs of func
|
||||
bench({
|
||||
name: "runs100ForIncrementX1e6",
|
||||
runs: 100,
|
||||
func(b: BenchmarkTimer) {
|
||||
b.start();
|
||||
for (let i: number = 0; i < 1e6; i++);
|
||||
b.stop();
|
||||
}
|
||||
});
|
||||
|
||||
// Itsabug
|
||||
bench(function throwing(b) {
|
||||
b.start();
|
||||
// Throws bc the timer's stop method is never called
|
||||
});
|
||||
|
||||
// Bench control
|
||||
runBenchmarks({ skip: /throw/ });
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
#### `bench(benchmark: BenchmarkDefinition | BenchmarkFunction): void`
|
||||
|
||||
Registers a benchmark that will be run once `runBenchmarks` is called.
|
||||
|
||||
#### `runBenchmarks(opts?: BenchmarkRunOptions): Promise<void>`
|
||||
|
||||
Runs all registered benchmarks serially. Filtering can be applied by setting
|
||||
`BenchmarkRunOptions.only` and/or `BenchmarkRunOptions.skip` to regular expressions matching benchmark names.
|
||||
|
||||
#### Other exports
|
||||
|
||||
```ts
|
||||
/** Provides methods for starting and stopping a benchmark clock. */
|
||||
export interface BenchmarkTimer {
|
||||
start: () => void;
|
||||
stop: () => void;
|
||||
}
|
||||
|
||||
/** Defines a benchmark through a named function. */
|
||||
export type BenchmarkFunction = {
|
||||
(b: BenchmarkTimer): void | Promise<void>;
|
||||
name: string;
|
||||
};
|
||||
|
||||
/** Defines a benchmark definition with configurable runs. */
|
||||
export interface BenchmarkDefinition {
|
||||
func: BenchmarkFunction;
|
||||
name: string;
|
||||
runs?: number;
|
||||
}
|
||||
|
||||
/** Defines runBenchmark's run constraints by matching benchmark names. */
|
||||
export interface BenchmarkRunOptions {
|
||||
only?: RegExp;
|
||||
skip?: RegExp;
|
||||
}
|
||||
```
|
50
benching/test.ts
Normal file
50
benching/test.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { test } from "../testing/mod.ts";
|
||||
import { bench, runBenchmarks, BenchmarkTimer } from "./mod.ts";
|
||||
|
||||
import "example.ts";
|
||||
|
||||
test(async function benching() {
|
||||
bench(function forIncrementX1e9(b: BenchmarkTimer) {
|
||||
b.start();
|
||||
for (let i: number = 0; i < 1e9; i++);
|
||||
b.stop();
|
||||
});
|
||||
|
||||
bench(function forDecrementX1e9(b: BenchmarkTimer) {
|
||||
b.start();
|
||||
for (let i: number = 1e9; i > 0; i--);
|
||||
b.stop();
|
||||
});
|
||||
|
||||
bench(async function forAwaitFetchDenolandX10(b: BenchmarkTimer) {
|
||||
b.start();
|
||||
for (let i: number = 0; i < 10; i++) {
|
||||
await fetch("https://deno.land/");
|
||||
}
|
||||
b.stop();
|
||||
});
|
||||
|
||||
bench(async function promiseAllFetchDenolandX10(b: BenchmarkTimer) {
|
||||
const urls = new Array(10).fill("https://deno.land/");
|
||||
b.start();
|
||||
await Promise.all(urls.map((denoland: string) => fetch(denoland)));
|
||||
b.stop();
|
||||
});
|
||||
|
||||
bench({
|
||||
name: "runs100ForIncrementX1e6",
|
||||
runs: 100,
|
||||
func(b: BenchmarkTimer) {
|
||||
b.start();
|
||||
for (let i: number = 0; i < 1e6; i++);
|
||||
b.stop();
|
||||
}
|
||||
});
|
||||
|
||||
bench(function throwing(b: BenchmarkTimer) {
|
||||
b.start();
|
||||
// Throws bc the timer's stop method is never called
|
||||
});
|
||||
|
||||
await runBenchmarks({ skip: /throw/ });
|
||||
});
|
Loading…
Reference in New Issue
Block a user