-
-
Notifications
You must be signed in to change notification settings - Fork 153
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(bench): add types, benchmark(), bigint timestamps, restructure
- split module into separate files - add BenchmarkOpts / BenchmarkResult types - add benchmark() - add now() timestamp fn (uses nanosec timer on Node) BREAKING CHANGE: Though no public API change, this library internally uses ES BigInt timestamps now (in Node via `process.hrtime.bigint()`).
- Loading branch information
1 parent
41bd4e4
commit e0af94c
Showing
10 changed files
with
302 additions
and
72 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
export type TimingResult<T> = [T, number]; | ||
|
||
export interface BenchmarkOpts { | ||
/** | ||
* Benchmark title (only used if `print` enabled) | ||
*/ | ||
title: string; | ||
/** | ||
* Number of iterations | ||
* | ||
* @defaultValue 1000 | ||
*/ | ||
iter: number; | ||
/** | ||
* Number of warmup iterations (not included in results). | ||
* | ||
* @defaultValue 10 | ||
*/ | ||
warmup: number; | ||
/** | ||
* If true, writes progress & results to console. | ||
* | ||
* @defaultValue true | ||
*/ | ||
print: boolean; | ||
} | ||
|
||
export interface BenchmarkResult { | ||
/** | ||
* Number of iterations | ||
*/ | ||
iter: number; | ||
/** | ||
* Total execution time for all runs (in ms) | ||
*/ | ||
total: number; | ||
/** | ||
* Mean execution time (in ms) | ||
*/ | ||
mean: number; | ||
/** | ||
* Median execution time (in ms) | ||
*/ | ||
median: number; | ||
/** | ||
* Min execution time (in ms) | ||
*/ | ||
min: number; | ||
/** | ||
* Max execution time (in ms) | ||
*/ | ||
max: number; | ||
/** | ||
* First quartile execution time (in ms). I.e. 25% of all runs were | ||
* faster/equal to this measurement. | ||
*/ | ||
q1: number; | ||
/** | ||
* Third quartile execution time (in ms). I.e. 25% of all runs were | ||
* equal/slower than this measurement. | ||
*/ | ||
q3: number; | ||
/** | ||
* Standard deviation (in percent) | ||
*/ | ||
sd: number; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { TimingResult } from "./api"; | ||
import { timed, timedResult } from "./timed"; | ||
|
||
/** | ||
* Executes given function `n` times, prints elapsed time to console and | ||
* returns last result from fn. The optional `prefix` will be displayed | ||
* with the output, allowing to label different measurements. | ||
* | ||
* @param fn - function to time | ||
* @param n - number of iterations | ||
*/ | ||
export const bench = <T>(fn: () => T, n = 1e6, prefix = "") => { | ||
let res: T; | ||
return timed(() => { | ||
while (n-- > 0) { | ||
res = fn(); | ||
} | ||
return res; | ||
}, prefix); | ||
}; | ||
|
||
/** | ||
* Similar to {@link bench}, but produces no output and instead returns | ||
* tuple of `fn`'s last result and the grand total time measurement. | ||
* | ||
* @param fn - function to time | ||
* @param n - number of iterations | ||
*/ | ||
export const benchResult = <T>(fn: () => T, n = 1e6): TimingResult<T> => { | ||
let res: T; | ||
return timedResult(() => { | ||
while (n-- > 0) { | ||
res = fn(); | ||
} | ||
return res; | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { BenchmarkOpts, BenchmarkResult } from "./api"; | ||
import { benchResult } from "./bench"; | ||
import { timedResult } from "./timed"; | ||
|
||
export const benchmark = ( | ||
fn: () => void, | ||
opts?: Partial<BenchmarkOpts> | ||
): BenchmarkResult => { | ||
opts = { title: "", iter: 1e3, warmup: 10, print: true, ...opts }; | ||
const { iter, warmup, print } = opts; | ||
print && console.log(`benchmarking: ${opts.title}`); | ||
const t = benchResult(fn, warmup)[1]; | ||
print && console.log(`\twarmup... ${t.toFixed(2)}ms (${warmup} runs)`); | ||
print && console.log("\texecuting..."); | ||
const samples: number[] = []; | ||
for (let i = iter!; --i >= 0; ) { | ||
samples.push(timedResult(fn)[1]); | ||
} | ||
samples.sort((a, b) => a - b); | ||
const total = samples.reduce((acc, x) => acc + x, 0); | ||
const mean = total / iter!; | ||
const median = samples[iter! >> 1]; | ||
const min = samples[0]; | ||
const max = samples[iter! - 1]; | ||
const q1 = samples[Math.ceil(iter! * 0.25)]; | ||
const q3 = samples[Math.ceil(iter! * 0.75)]; | ||
const sd = | ||
(Math.sqrt( | ||
samples.reduce((acc, x) => acc + (mean - x) ** 2, 0) / iter! | ||
) / | ||
mean) * | ||
100; | ||
if (print) { | ||
console.log(`\ttotal: ${total.toFixed(2)}ms, runs: ${iter}`); | ||
console.log( | ||
`\tmean: ${mean.toFixed(2)}ms, median: ${median.toFixed( | ||
2 | ||
)}ms, range: [${min.toFixed(2)}..${max.toFixed(2)}]` | ||
); | ||
console.log(`\tq1: ${q1.toFixed(2)}ms, q3: ${q3.toFixed(2)}ms`); | ||
console.log(`\tsd: ${sd.toFixed(2)}%`); | ||
} | ||
return { | ||
iter: iter!, | ||
total, | ||
mean, | ||
median, | ||
min, | ||
max, | ||
q1, | ||
q3, | ||
sd | ||
}; | ||
}; |
Oops, something went wrong.