149 |
150 | {state.title}
151 |
155 |
156 |
157 |
158 |
169 |
170 | {progress && progress.time > 0 && (
171 |
175 | )}
176 | {error && (
177 |
178 | {error.constructor.name}: {error.message}
179 |
180 | )}
181 | {results && (
182 |
183 |
184 | {results.map((result, i) => (
185 |
186 | {result.name} |
187 | {result.minExecutionTime.toFixed(4)} µs |
188 | |
197 |
198 | ))}
199 |
200 |
201 | )}
202 |
205 |
206 | )
207 | }
208 |
--------------------------------------------------------------------------------
/src/benchmark/index.tsx:
--------------------------------------------------------------------------------
1 | import deepEqual from 'deep-equal'
2 |
3 | export type InitialValuesFunction = () => any
4 | export type TestFunction = (input: any) => any
5 | export type EqualFunction = (a: any, b: any) => any
6 | export type BenchmarkInput = { initialValues: InitialValuesFunction; equal?: EqualFunction, tests: TestFunction[] }
7 |
8 | export type Microseconds = number
9 |
10 | export type ProgressCallback = (status: string, progress: number) => void
11 | export type Results = Array<{
12 | name: string
13 | minExecutionTime: Microseconds
14 | executionTimes: Microseconds[]
15 | outputs: any[]
16 | }>
17 |
18 | function defaultEqual(a: any, b: any) {
19 | // TODO: provide custom comparator to deepEqual
20 | if (typeof a === 'number' && typeof b === 'number') {
21 | return Math.abs(a - b) < 0.00000001
22 | } else {
23 | return deepEqual(a, b)
24 | }
25 | }
26 |
27 | export function checkSoundness({ initialValues, equal = defaultEqual, tests }: BenchmarkInput) {
28 | if (deepEqual(initialValues(), initialValues())) {
29 | throw new Error('initialValue() must return random values!')
30 | }
31 | if (!tests.length) {
32 | throw new Error('Define at least one test function')
33 | }
34 | for (const test of tests) {
35 | if (typeof test !== 'function') {
36 | throw new Error(`All tests must be functions!`)
37 | }
38 | if (!test.name) {
39 | throw new Error('All test functions must have names')
40 | }
41 | const seed = initialValues()
42 | if (!equal(test(seed[test.name]), test(seed[test.name]))) {
43 | throw new Error(
44 | `${test.name}() must depend only on its input (seems that it returns random results...)`
45 | )
46 | }
47 | if (equal(test(initialValues()[test.name]), test(initialValues()[test.name]))) {
48 | throw new Error(
49 | `${test.name}() must depend on its input (seems that it returns the same result regardless of that...)`
50 | )
51 | }
52 | }
53 | const seed = initialValues()
54 | const result0 = tests[0](seed[tests[0].name])
55 | for (const test of tests.slice(1)) {
56 | const result = test(seed[test.name])
57 | if (!equal(result, result0)) {
58 | throw new Error(`The output of ${test.name}() must be the same as of ${tests[0].name}()`)
59 | }
60 | }
61 | }
62 |
63 | const microsecondsPerSample = 100 * 1000
64 |
65 | async function quicklyDetermineRoughAverageExecutionTime(
66 | test: TestFunction,
67 | theInitialValue: any
68 | ): Promise