@effect-bench/core 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -3
- package/dist/src/Bench.d.ts +63 -2
- package/dist/src/Bench.d.ts.map +1 -1
- package/dist/src/Bench.js +69 -7
- package/dist/src/Bench.js.map +1 -1
- package/dist/src/BenchResult.d.ts +15 -7
- package/dist/src/BenchResult.d.ts.map +1 -1
- package/dist/src/BenchResult.js +10 -2
- package/dist/src/BenchResult.js.map +1 -1
- package/dist/src/Measure.d.ts +11 -6
- package/dist/src/Measure.d.ts.map +1 -1
- package/dist/src/Measure.js +157 -34
- package/dist/src/Measure.js.map +1 -1
- package/dist/src/Reporter.d.ts.map +1 -1
- package/dist/src/Reporter.js +12 -1
- package/dist/src/Reporter.js.map +1 -1
- package/dist/src/Runner.d.ts +20 -6
- package/dist/src/Runner.d.ts.map +1 -1
- package/dist/src/Runner.js +46 -22
- package/dist/src/Runner.js.map +1 -1
- package/dist/src/Services.d.ts +8 -0
- package/dist/src/Services.d.ts.map +1 -1
- package/dist/src/Services.js +4 -1
- package/dist/src/Services.js.map +1 -1
- package/dist/src/Stats.d.ts +1 -0
- package/dist/src/Stats.d.ts.map +1 -1
- package/dist/src/Stats.js +10 -0
- package/dist/src/Stats.js.map +1 -1
- package/package.json +1 -1
- package/src/Bench.ts +211 -15
- package/src/BenchResult.ts +13 -2
- package/src/Measure.ts +179 -35
- package/src/Reporter.ts +15 -1
- package/src/Runner.ts +48 -22
- package/src/Services.ts +13 -1
- package/src/Stats.ts +11 -0
package/dist/src/Services.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Services.js","sourceRoot":"","sources":["../../src/Services.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAOtD,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC,SAAS,CAAQ,yBAAyB,EAAE;IAC5E,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC;CAC/D,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"Services.js","sourceRoot":"","sources":["../../src/Services.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAOtD,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC,SAAS,CAAQ,yBAAyB,EAAE;IAC5E,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC;CAC/D,CAAC,CAAA;AASF,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,SAAS,CAAS,0BAA0B,EAAE;IAC/E,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;CAC7E,CAAC,CAAA;AAQF,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,SAAS,CAAgB,0BAA0B,EAAE;IACtF,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC;CACrD,CAAC,CAAA;AAMF,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAmB,+BAA+B,EAAE;IACnG,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC;YAC1B,OAAO,EAAE,SAAS;YAClB,cAAc,EAAE,SAAS;YACzB,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,SAAS;YACf,GAAG,EAAE,SAAS;YACd,IAAI,EAAE,CAAC;YACP,MAAM,EAAE,IAAI;YACZ,QAAQ,EAAE,IAAI;SACf,CAAC;KACH,CAAC;CACH,CAAC,CAAA;AAEF,MAAM,OAAO,aAAc,SAAQ,IAAI,CAAC,WAAW,CAAC,eAAe,CAIjE;CAAG;AAOL,MAAM,CAAC,MAAM,eAAe,GAAG,GAAU,EAAE;IACzC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAA;IAC7C,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAChF,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;KAC7E,CAAA;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC,SAAS,CAAQ,4BAA4B,EAAE;IAClF,YAAY,EAAE,eAAe;CAC9B,CAAC,CAAA;AAOF,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC,SAAS,CAAW,4BAA4B,EAAE;IACrF,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;CAChF,CAAC,CAAA"}
|
package/dist/src/Stats.d.ts
CHANGED
|
@@ -4,4 +4,5 @@ export declare const median: (values: ReadonlyArray<number>) => number;
|
|
|
4
4
|
/** Drops Tukey outliers (outside 1.5 × IQR) before summarising. */
|
|
5
5
|
export declare const compute: (samplesNs: ReadonlyArray<number>) => StatsType;
|
|
6
6
|
export declare const formatNs: (ns: number) => string;
|
|
7
|
+
export declare const formatBytes: (bytes: number) => string;
|
|
7
8
|
//# sourceMappingURL=Stats.d.ts.map
|
package/dist/src/Stats.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Stats.d.ts","sourceRoot":"","sources":["../../src/Stats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAE1D,eAAO,MAAM,QAAQ,WAAY,aAAa,CAAC,MAAM,CAAC,KAAK,MAAM,KAAG,MAMnE,CAAA;AAED,eAAO,MAAM,MAAM,WAAY,aAAa,CAAC,MAAM,CAAC,KAAG,MAIpD,CAAA;AAEH,mEAAmE;AACnE,eAAO,MAAM,OAAO,cAAe,aAAa,CAAC,MAAM,CAAC,KAAG,SAuB1D,CAAA;AAQD,eAAO,MAAM,QAAQ,OAAQ,MAAM,KAAG,MAGrC,CAAA"}
|
|
1
|
+
{"version":3,"file":"Stats.d.ts","sourceRoot":"","sources":["../../src/Stats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAE1D,eAAO,MAAM,QAAQ,WAAY,aAAa,CAAC,MAAM,CAAC,KAAK,MAAM,KAAG,MAMnE,CAAA;AAED,eAAO,MAAM,MAAM,WAAY,aAAa,CAAC,MAAM,CAAC,KAAG,MAIpD,CAAA;AAEH,mEAAmE;AACnE,eAAO,MAAM,OAAO,cAAe,aAAa,CAAC,MAAM,CAAC,KAAG,SAuB1D,CAAA;AAQD,eAAO,MAAM,QAAQ,OAAQ,MAAM,KAAG,MAGrC,CAAA;AAED,eAAO,MAAM,WAAW,UAAW,MAAM,KAAG,MAS3C,CAAA"}
|
package/dist/src/Stats.js
CHANGED
|
@@ -43,4 +43,14 @@ export const formatNs = (ns) => {
|
|
|
43
43
|
return `${(ns / scale).toPrecision(3)} ${unit}`;
|
|
44
44
|
return `${ns.toPrecision(3)} ns`;
|
|
45
45
|
};
|
|
46
|
+
export const formatBytes = (bytes) => {
|
|
47
|
+
const units = ['B', 'KiB', 'MiB', 'GiB'];
|
|
48
|
+
let value = Math.abs(bytes);
|
|
49
|
+
let unit = 0;
|
|
50
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
51
|
+
value /= 1024;
|
|
52
|
+
unit++;
|
|
53
|
+
}
|
|
54
|
+
return `${bytes < 0 ? '-' : ''}${unit === 0 ? value.toFixed(0) : value.toPrecision(3)} ${units[unit]}`;
|
|
55
|
+
};
|
|
46
56
|
//# sourceMappingURL=Stats.js.map
|
package/dist/src/Stats.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Stats.js","sourceRoot":"","sources":["../../src/Stats.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Stats.js","sourceRoot":"","sources":["../../src/Stats.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/Bench.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import type { Duration, Scope } from 'effect'
|
|
2
|
-
import { Effect, Layer } from 'effect'
|
|
2
|
+
import { Effect, Layer, Stream } from 'effect'
|
|
3
|
+
import { BenchClock, BenchPhases } from './Services.ts'
|
|
3
4
|
|
|
4
5
|
export type ParamValue = string | number | boolean
|
|
5
6
|
|
|
6
7
|
export type Params = Readonly<Record<string, ParamValue>>
|
|
7
8
|
|
|
9
|
+
export type Size = number | `${number} ${'B' | 'KB' | 'KiB' | 'MB' | 'MiB' | 'GB' | 'GiB'}`
|
|
10
|
+
|
|
8
11
|
export interface Budget {
|
|
9
12
|
readonly p50?: Duration.Input
|
|
10
13
|
readonly p99?: Duration.Input
|
|
14
|
+
/** Heap growth per operation. Setting it turns on `memory`. */
|
|
15
|
+
readonly heapDelta?: Size
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
export interface Options {
|
|
@@ -24,7 +29,26 @@ export interface Options {
|
|
|
24
29
|
|
|
25
30
|
export type Operation =
|
|
26
31
|
| { readonly _tag: 'Sync'; readonly run: () => unknown }
|
|
27
|
-
| {
|
|
32
|
+
| {
|
|
33
|
+
readonly _tag: 'Effect'
|
|
34
|
+
readonly run: Effect.Effect<unknown, unknown>
|
|
35
|
+
/** Operations one run performs; stats are reported per operation. */
|
|
36
|
+
readonly units?: number
|
|
37
|
+
/** A single operation, for `check`, when `run` performs several. */
|
|
38
|
+
readonly single?: Effect.Effect<unknown, unknown>
|
|
39
|
+
}
|
|
40
|
+
| {
|
|
41
|
+
readonly _tag: 'PerOp'
|
|
42
|
+
readonly before: Effect.Effect<unknown, unknown>
|
|
43
|
+
readonly run: (input: unknown) => Effect.Effect<unknown, unknown>
|
|
44
|
+
}
|
|
45
|
+
| { readonly _tag: 'Stream'; readonly stream: () => Stream.Stream<unknown, unknown> }
|
|
46
|
+
| {
|
|
47
|
+
readonly _tag: 'Scenario'
|
|
48
|
+
readonly before: Effect.Effect<unknown, unknown> | undefined
|
|
49
|
+
readonly run: (input: unknown) => Effect.Effect<unknown, unknown>
|
|
50
|
+
readonly verify: ((value: unknown) => boolean) | undefined
|
|
51
|
+
}
|
|
28
52
|
|
|
29
53
|
export interface Bench<in Ctx = any, out R = any> {
|
|
30
54
|
readonly _tag: 'Bench'
|
|
@@ -32,6 +56,8 @@ export interface Bench<in Ctx = any, out R = any> {
|
|
|
32
56
|
readonly params: Params
|
|
33
57
|
readonly group: { readonly name: string; readonly baseline: string } | undefined
|
|
34
58
|
readonly options: Options
|
|
59
|
+
/** Defaults for this kind of bench; mode, suite and bench options override them. */
|
|
60
|
+
readonly defaults: Options
|
|
35
61
|
readonly check: ((value: unknown) => boolean) | undefined
|
|
36
62
|
readonly prepare: (ctx: Ctx) => Effect.Effect<Operation, unknown, R>
|
|
37
63
|
}
|
|
@@ -73,26 +99,77 @@ const makeBench = <Ctx, R>(
|
|
|
73
99
|
name: string,
|
|
74
100
|
prepare: (ctx: Ctx) => Effect.Effect<Operation, unknown, R>,
|
|
75
101
|
{ check, ...options }: BenchOptions<any>,
|
|
76
|
-
params: Params = {}
|
|
77
|
-
): Bench<Ctx, R> => ({ _tag: 'Bench', name, params, group: undefined, options, check, prepare })
|
|
102
|
+
{ params = {}, defaults = {} }: { readonly params?: Params; readonly defaults?: Options } = {}
|
|
103
|
+
): Bench<Ctx, R> => ({ _tag: 'Bench', name, params, group: undefined, options, defaults, check, prepare })
|
|
104
|
+
|
|
105
|
+
export interface Spec<Ctx, I, B, A, E, R> extends BenchOptions<A> {
|
|
106
|
+
/** Runs once, untimed. */
|
|
107
|
+
readonly prepare?: (ctx: Ctx) => Effect.Effect<I, unknown, R>
|
|
108
|
+
/** Runs before every operation, untimed; `run` receives its value. */
|
|
109
|
+
readonly before?: (input: I, ctx: Ctx) => Effect.Effect<B, unknown, R>
|
|
110
|
+
readonly run: (input: B, ctx: Ctx) => Effect.Effect<A, E, R>
|
|
111
|
+
}
|
|
78
112
|
|
|
79
113
|
/**
|
|
80
114
|
* An Effect value is re-run as is; a function is called for every operation, so building the Effect is measured too.
|
|
81
115
|
*/
|
|
82
|
-
export const make
|
|
116
|
+
export const make: {
|
|
117
|
+
<Ctx = unknown, A = unknown, E = never, R = never>(
|
|
118
|
+
name: string,
|
|
119
|
+
run: Effect.Effect<A, E, R> | ((ctx: Ctx) => Effect.Effect<A, E, R>),
|
|
120
|
+
options?: BenchOptions<A>
|
|
121
|
+
): Bench<Ctx, R>
|
|
122
|
+
<Ctx = unknown, I = undefined, B = I, A = unknown, E = never, R = never>(
|
|
123
|
+
name: string,
|
|
124
|
+
spec: Spec<Ctx, I, B, A, E, R>
|
|
125
|
+
): Bench<Ctx, R>
|
|
126
|
+
} = (
|
|
83
127
|
name: string,
|
|
84
|
-
run:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
128
|
+
run:
|
|
129
|
+
| Effect.Effect<unknown, unknown, unknown>
|
|
130
|
+
| ((ctx: unknown) => Effect.Effect<unknown, unknown, unknown>)
|
|
131
|
+
| Spec<unknown, unknown, unknown, unknown, unknown, unknown>,
|
|
132
|
+
options: BenchOptions<unknown> = {}
|
|
133
|
+
): Bench<any, any> => {
|
|
134
|
+
if (Effect.isEffect(run) || typeof run === 'function') {
|
|
135
|
+
return makeBench(
|
|
136
|
+
name,
|
|
137
|
+
ctx =>
|
|
138
|
+
Effect.map(Effect.context<unknown>(), context => ({
|
|
139
|
+
_tag: 'Effect' as const,
|
|
140
|
+
run: Effect.provideContext(Effect.isEffect(run) ? run : Effect.suspend(() => run(ctx)), context),
|
|
141
|
+
})),
|
|
142
|
+
options
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
const { prepare, before, run: runOne, ...specOptions } = run
|
|
146
|
+
return makeBench(
|
|
88
147
|
name,
|
|
89
148
|
ctx =>
|
|
90
|
-
Effect.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
149
|
+
Effect.gen(function* () {
|
|
150
|
+
const input = prepare === undefined ? undefined : yield* prepare(ctx)
|
|
151
|
+
const context = yield* Effect.context<unknown>()
|
|
152
|
+
if (before === undefined) {
|
|
153
|
+
return {
|
|
154
|
+
_tag: 'Effect' as const,
|
|
155
|
+
run: Effect.provideContext(
|
|
156
|
+
Effect.suspend(() => runOne(input, ctx)),
|
|
157
|
+
context
|
|
158
|
+
),
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
_tag: 'PerOp' as const,
|
|
163
|
+
before: Effect.provideContext(
|
|
164
|
+
Effect.suspend(() => before(input, ctx)),
|
|
165
|
+
context
|
|
166
|
+
),
|
|
167
|
+
run: (value: unknown) => Effect.provideContext(runOne(value, ctx), context),
|
|
168
|
+
}
|
|
169
|
+
}),
|
|
170
|
+
specOptions
|
|
95
171
|
)
|
|
172
|
+
}
|
|
96
173
|
|
|
97
174
|
export const sync = <Ctx = unknown, A = unknown>(
|
|
98
175
|
name: string,
|
|
@@ -151,10 +228,129 @@ export const params: {
|
|
|
151
228
|
return { _tag: 'Effect' as const, run: Effect.provideContext(run!(input, ctx), context) }
|
|
152
229
|
}),
|
|
153
230
|
options,
|
|
154
|
-
combo
|
|
231
|
+
{ params: combo }
|
|
155
232
|
)
|
|
156
233
|
)
|
|
157
234
|
|
|
235
|
+
export interface ThroughputSpec<Ctx, I, A, E, R> extends BenchOptions<A> {
|
|
236
|
+
/** Operations per run; stats are reported per operation. */
|
|
237
|
+
readonly ops: number
|
|
238
|
+
/** Fibers running operations at once. An array makes one bench per value. */
|
|
239
|
+
readonly concurrency: number | ReadonlyArray<number>
|
|
240
|
+
readonly prepare?: (ctx: Ctx) => Effect.Effect<I, unknown, R>
|
|
241
|
+
readonly run: (index: number, input: I, ctx: Ctx) => Effect.Effect<A, E, R>
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Runs `ops` operations with the given concurrency; ids carry it, e.g. `enqueue[concurrency=50]`. */
|
|
245
|
+
export const throughput = <Ctx = unknown, I = undefined, A = unknown, E = never, R = never>(
|
|
246
|
+
name: string,
|
|
247
|
+
{ ops, concurrency, prepare, run, ...options }: ThroughputSpec<Ctx, I, A, E, R>
|
|
248
|
+
): ReadonlyArray<Bench<Ctx, R>> => {
|
|
249
|
+
const indexes = Array.from({ length: ops }, (_, index) => index)
|
|
250
|
+
return (typeof concurrency === 'number' ? [concurrency] : concurrency).map(fibers =>
|
|
251
|
+
makeBench(
|
|
252
|
+
name,
|
|
253
|
+
(ctx: Ctx) =>
|
|
254
|
+
Effect.gen(function* () {
|
|
255
|
+
const input = (prepare === undefined ? undefined : yield* prepare(ctx)) as I
|
|
256
|
+
const context = yield* Effect.context<R>()
|
|
257
|
+
return {
|
|
258
|
+
_tag: 'Effect' as const,
|
|
259
|
+
units: ops,
|
|
260
|
+
single: Effect.provideContext(
|
|
261
|
+
Effect.suspend(() => run(0, input, ctx)),
|
|
262
|
+
context
|
|
263
|
+
),
|
|
264
|
+
run: Effect.provideContext(
|
|
265
|
+
Effect.forEach(indexes, index => run(index, input, ctx), { concurrency: fibers, discard: true }),
|
|
266
|
+
context
|
|
267
|
+
),
|
|
268
|
+
}
|
|
269
|
+
}),
|
|
270
|
+
options,
|
|
271
|
+
{ params: { concurrency: fibers } }
|
|
272
|
+
)
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export interface StreamSpec<Ctx, I, A, E, R> extends Options {
|
|
277
|
+
readonly prepare?: (ctx: Ctx) => Effect.Effect<I, unknown, R>
|
|
278
|
+
/** Built fresh for every run. */
|
|
279
|
+
readonly stream: (input: I, ctx: Ctx) => Stream.Stream<A, E, R>
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Runs the stream to the end; stats are per item, with time to first item and peak RSS as metrics. */
|
|
283
|
+
export const stream = <Ctx = unknown, I = undefined, A = unknown, E = never, R = never>(
|
|
284
|
+
name: string,
|
|
285
|
+
{ prepare, stream: build, ...options }: StreamSpec<Ctx, I, A, E, R>
|
|
286
|
+
): Bench<Ctx, R> =>
|
|
287
|
+
makeBench(
|
|
288
|
+
name,
|
|
289
|
+
(ctx: Ctx) =>
|
|
290
|
+
Effect.gen(function* () {
|
|
291
|
+
const input = (prepare === undefined ? undefined : yield* prepare(ctx)) as I
|
|
292
|
+
const context = yield* Effect.context<R>()
|
|
293
|
+
return { _tag: 'Stream' as const, stream: () => Stream.provideContext(build(input, ctx), context) }
|
|
294
|
+
}),
|
|
295
|
+
options,
|
|
296
|
+
{ defaults: { samples: 10 } }
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
export interface ScenarioSpec<Ctx, I, B, A, E, R> extends Options {
|
|
300
|
+
/** Runs once, untimed. */
|
|
301
|
+
readonly prepare?: (ctx: Ctx) => Effect.Effect<I, unknown, R>
|
|
302
|
+
/** Runs before every run, untimed; `run` receives its value. */
|
|
303
|
+
readonly before?: (input: I, ctx: Ctx) => Effect.Effect<B, unknown, R>
|
|
304
|
+
readonly run: (input: B, ctx: Ctx) => Effect.Effect<A, E, R>
|
|
305
|
+
/** Checked after every run, untimed. */
|
|
306
|
+
readonly verify?: (value: A) => boolean
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** A longer flow timed once per sample, with `Bench.phase` timings as metrics. */
|
|
310
|
+
export const scenario = <Ctx = unknown, I = undefined, B = I, A = unknown, E = never, R = never>(
|
|
311
|
+
name: string,
|
|
312
|
+
{ prepare, before, run, verify, ...options }: ScenarioSpec<Ctx, I, B, A, E, R>
|
|
313
|
+
): Bench<Ctx, R> =>
|
|
314
|
+
makeBench(
|
|
315
|
+
name,
|
|
316
|
+
(ctx: Ctx) =>
|
|
317
|
+
Effect.gen(function* () {
|
|
318
|
+
const input = (prepare === undefined ? undefined : yield* prepare(ctx)) as I
|
|
319
|
+
const context = yield* Effect.context<R>()
|
|
320
|
+
return {
|
|
321
|
+
_tag: 'Scenario' as const,
|
|
322
|
+
before:
|
|
323
|
+
before === undefined
|
|
324
|
+
? undefined
|
|
325
|
+
: Effect.provideContext(
|
|
326
|
+
Effect.suspend(() => before(input, ctx)),
|
|
327
|
+
context
|
|
328
|
+
),
|
|
329
|
+
run: (value: unknown) =>
|
|
330
|
+
Effect.provideContext(
|
|
331
|
+
Effect.suspend(() => run((before === undefined ? input : value) as B, ctx)),
|
|
332
|
+
context
|
|
333
|
+
),
|
|
334
|
+
verify: verify as ((value: unknown) => boolean) | undefined,
|
|
335
|
+
}
|
|
336
|
+
}),
|
|
337
|
+
options,
|
|
338
|
+
{ defaults: { samples: 10, minTime: 0, warmup: 0 } }
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
/** Times part of a scenario. Nested phases are reported as `outer/inner`; outside a scenario it just runs the effect. */
|
|
342
|
+
export const phase = <A, E, R>(name: string, effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
|
|
343
|
+
Effect.gen(function* () {
|
|
344
|
+
const recorder = yield* BenchPhases
|
|
345
|
+
const clock = yield* BenchClock
|
|
346
|
+
const path = recorder.path === '' ? name : `${recorder.path}/${name}`
|
|
347
|
+
const startedAt = clock.nowNs()
|
|
348
|
+
return yield* effect.pipe(
|
|
349
|
+
Effect.provideService(BenchPhases, { ...recorder, path }),
|
|
350
|
+
Effect.ensuring(Effect.sync(() => recorder.record(path, clock.nowNs() - startedAt)))
|
|
351
|
+
)
|
|
352
|
+
})
|
|
353
|
+
|
|
158
354
|
export const group =
|
|
159
355
|
(name: string, options: { readonly baseline: string }) =>
|
|
160
356
|
<Ctx, R>(...entries: ReadonlyArray<Entry<Ctx, R>>): ReadonlyArray<Bench<Ctx, R>> =>
|
package/src/BenchResult.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Schema } from 'effect'
|
|
2
2
|
|
|
3
|
-
export const schemaVersion =
|
|
3
|
+
export const schemaVersion = 3
|
|
4
4
|
|
|
5
5
|
export const Stats = Schema.Struct({
|
|
6
6
|
samples: Schema.Number,
|
|
@@ -56,6 +56,9 @@ export const Relative = Schema.Struct({
|
|
|
56
56
|
})
|
|
57
57
|
export type Relative = typeof Relative.Type
|
|
58
58
|
|
|
59
|
+
/** Extra numbers per bench, such as stream time to first item or scenario phases; the key suffix gives the unit. */
|
|
60
|
+
export const Metrics = Schema.NullOr(Schema.Record(Schema.String, Schema.Number))
|
|
61
|
+
|
|
59
62
|
const identity = {
|
|
60
63
|
id: Schema.String,
|
|
61
64
|
suite: Schema.String,
|
|
@@ -74,6 +77,7 @@ export const OkResult = Schema.Struct({
|
|
|
74
77
|
batchSize: Schema.Number,
|
|
75
78
|
overheadNs: Schema.Number,
|
|
76
79
|
heapDeltaBytes: Schema.NullOr(Schema.Number),
|
|
80
|
+
metrics: Metrics,
|
|
77
81
|
budget: Schema.NullOr(BudgetCheck),
|
|
78
82
|
comparison: Schema.NullOr(Comparison),
|
|
79
83
|
relative: Schema.NullOr(Relative),
|
|
@@ -100,7 +104,14 @@ export const RawOk = Schema.Struct({
|
|
|
100
104
|
batchSize: Schema.Number,
|
|
101
105
|
overheadNs: Schema.Number,
|
|
102
106
|
heapDeltaBytes: Schema.NullOr(Schema.Number),
|
|
103
|
-
|
|
107
|
+
metrics: Metrics,
|
|
108
|
+
limits: Schema.NullOr(
|
|
109
|
+
Schema.Struct({
|
|
110
|
+
p50Ns: Schema.NullOr(Schema.Number),
|
|
111
|
+
p99Ns: Schema.NullOr(Schema.Number),
|
|
112
|
+
heapDeltaBytes: Schema.NullOr(Schema.Number),
|
|
113
|
+
})
|
|
114
|
+
),
|
|
104
115
|
})
|
|
105
116
|
export type RawOk = typeof RawOk.Type
|
|
106
117
|
|
package/src/Measure.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Effect } from 'effect'
|
|
1
|
+
import { Data, Effect, Stream } from 'effect'
|
|
2
2
|
import type { Operation } from './Bench.ts'
|
|
3
|
-
import { BenchClock, BenchMemory } from './Services.ts'
|
|
3
|
+
import { BenchClock, BenchMemory, BenchPhases, type Clock, type Memory } from './Services.ts'
|
|
4
4
|
import { median } from './Stats.ts'
|
|
5
5
|
|
|
6
6
|
export interface MeasureOptions {
|
|
@@ -19,78 +19,222 @@ export interface Measurement {
|
|
|
19
19
|
readonly batchSize: number
|
|
20
20
|
readonly overheadNs: number
|
|
21
21
|
readonly heapDeltaBytes: number | null
|
|
22
|
+
readonly metrics: Readonly<Record<string, number>> | null
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
/** A scenario's `verify` rejected a run's result. */
|
|
26
|
+
export class CheckFailed extends Data.TaggedError('CheckFailed')<{ readonly message: string }> {}
|
|
27
|
+
|
|
24
28
|
/** Keeps return values reachable so the JIT cannot drop the measured work. */
|
|
25
29
|
export let blackhole: unknown
|
|
26
30
|
|
|
31
|
+
interface Timer {
|
|
32
|
+
/** Returns the start time. */
|
|
33
|
+
readonly start: () => number
|
|
34
|
+
readonly stop: () => number
|
|
35
|
+
readonly heapDeltas: Array<number>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** With memory, collects garbage before starting and records heap growth on stop. */
|
|
39
|
+
const makeTimer = (clock: Clock, memory: Memory | undefined): Timer => {
|
|
40
|
+
let startedAt = 0
|
|
41
|
+
let heapBefore = 0
|
|
42
|
+
const heapDeltas: Array<number> = []
|
|
43
|
+
return {
|
|
44
|
+
heapDeltas,
|
|
45
|
+
start: () => {
|
|
46
|
+
memory?.gc?.()
|
|
47
|
+
heapBefore = memory?.heapUsed?.() ?? 0
|
|
48
|
+
startedAt = clock.nowNs()
|
|
49
|
+
return startedAt
|
|
50
|
+
},
|
|
51
|
+
stop: () => {
|
|
52
|
+
const elapsed = clock.nowNs() - startedAt
|
|
53
|
+
if (memory?.heapUsed !== undefined) heapDeltas.push(memory.heapUsed() - heapBefore)
|
|
54
|
+
return elapsed
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type Batched = Extract<Operation, { readonly _tag: 'Sync' | 'Effect' | 'PerOp' }>
|
|
60
|
+
type SingleShot = Extract<Operation, { readonly _tag: 'Stream' | 'Scenario' }>
|
|
61
|
+
|
|
62
|
+
/** Returns the elapsed time of the timed part only; `PerOp` inputs are built before the timer starts. */
|
|
27
63
|
const makeBatch =
|
|
28
|
-
(operation:
|
|
29
|
-
(n: number): Effect.Effect<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
64
|
+
(operation: Batched) =>
|
|
65
|
+
(n: number, timer: Timer): Effect.Effect<number, unknown> => {
|
|
66
|
+
switch (operation._tag) {
|
|
67
|
+
case 'Sync': {
|
|
68
|
+
const run = operation.run
|
|
69
|
+
return Effect.sync(() => {
|
|
70
|
+
timer.start()
|
|
71
|
+
for (let i = 0; i < n; i++) blackhole = run()
|
|
72
|
+
return timer.stop()
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
case 'Effect': {
|
|
76
|
+
const run = operation.run
|
|
77
|
+
return Effect.gen(function* () {
|
|
78
|
+
timer.start()
|
|
79
|
+
for (let i = 0; i < n; i++) blackhole = yield* run
|
|
80
|
+
return timer.stop()
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
case 'PerOp': {
|
|
84
|
+
const { before, run } = operation
|
|
85
|
+
return Effect.gen(function* () {
|
|
86
|
+
const inputs: Array<unknown> = []
|
|
87
|
+
for (let i = 0; i < n; i++) inputs.push(yield* before)
|
|
88
|
+
timer.start()
|
|
89
|
+
for (const input of inputs) blackhole = yield* run(input)
|
|
90
|
+
return timer.stop()
|
|
91
|
+
})
|
|
92
|
+
}
|
|
35
93
|
}
|
|
36
|
-
const run = operation.run
|
|
37
|
-
return Effect.gen(function* () {
|
|
38
|
-
for (let i = 0; i < n; i++) blackhole = yield* run
|
|
39
|
-
})
|
|
40
94
|
}
|
|
41
95
|
|
|
42
|
-
const emptyOperation = (operation:
|
|
43
|
-
operation._tag
|
|
96
|
+
const emptyOperation = (operation: Batched): Batched => {
|
|
97
|
+
switch (operation._tag) {
|
|
98
|
+
case 'Sync':
|
|
99
|
+
return { _tag: 'Sync', run: () => undefined }
|
|
100
|
+
case 'Effect':
|
|
101
|
+
return { _tag: 'Effect', run: Effect.void }
|
|
102
|
+
case 'PerOp':
|
|
103
|
+
return { _tag: 'PerOp', before: Effect.void, run: () => Effect.void }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
44
106
|
|
|
45
|
-
|
|
107
|
+
const measureBatched = Effect.fn('effectBench/measureBatched')(function* (operation: Batched, options: MeasureOptions) {
|
|
46
108
|
const clock = yield* BenchClock
|
|
47
109
|
const memory = yield* BenchMemory
|
|
48
110
|
const targetSampleNs = options.targetSampleNs ?? 1_000_000
|
|
49
111
|
const maxSamples = options.maxSamples ?? 10_000
|
|
112
|
+
const units = operation._tag === 'Effect' ? (operation.units ?? 1) : 1
|
|
50
113
|
const batch = makeBatch(operation)
|
|
51
114
|
const empty = makeBatch(emptyOperation(operation))
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
Effect.suspend(() => {
|
|
55
|
-
const startedAt = clock.nowNs()
|
|
56
|
-
return Effect.map(run(n), () => clock.nowNs() - startedAt)
|
|
57
|
-
})
|
|
115
|
+
const plain = makeTimer(clock, undefined)
|
|
116
|
+
const sampling = makeTimer(clock, options.memory ? memory : undefined)
|
|
58
117
|
|
|
59
118
|
const warmupStartedAt = clock.nowNs()
|
|
60
|
-
do yield* batch(1)
|
|
119
|
+
do yield* batch(1, plain)
|
|
61
120
|
while (clock.nowNs() - warmupStartedAt < options.warmupNs)
|
|
62
121
|
|
|
63
122
|
let batchSize = 1
|
|
64
123
|
for (;;) {
|
|
65
|
-
const elapsed = yield*
|
|
124
|
+
const elapsed = yield* batch(batchSize, plain)
|
|
66
125
|
if (elapsed >= targetSampleNs || batchSize >= 2 ** 30) break
|
|
67
126
|
batchSize = Math.min(batchSize * 100, Math.max(batchSize * 2, Math.ceil((batchSize * targetSampleNs) / elapsed)))
|
|
68
127
|
}
|
|
69
128
|
|
|
70
129
|
const overheads: Array<number> = []
|
|
71
|
-
for (let i = 0; i < 5; i++) overheads.push(yield*
|
|
130
|
+
for (let i = 0; i < 5; i++) overheads.push(yield* empty(batchSize, plain))
|
|
72
131
|
const overheadNs = median(overheads)
|
|
73
132
|
|
|
133
|
+
const operations = batchSize * units
|
|
74
134
|
const samplesNs: Array<number> = []
|
|
75
|
-
const heapDeltas: Array<number> = []
|
|
76
|
-
const gc = options.memory ? memory.gc : undefined
|
|
77
|
-
const heapUsed = options.memory ? memory.heapUsed : undefined
|
|
78
135
|
const startedAt = clock.nowNs()
|
|
79
136
|
while (
|
|
80
137
|
samplesNs.length < maxSamples &&
|
|
81
138
|
(samplesNs.length < options.samples || clock.nowNs() - startedAt < options.minTimeNs)
|
|
82
139
|
) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const elapsed = yield* time(batch, batchSize)
|
|
86
|
-
if (heapBefore !== undefined) heapDeltas.push((heapUsed!() - heapBefore) / batchSize)
|
|
87
|
-
samplesNs.push(Math.max(0, elapsed - overheadNs) / batchSize)
|
|
140
|
+
const elapsed = yield* batch(batchSize, sampling)
|
|
141
|
+
samplesNs.push(Math.max(0, elapsed - overheadNs) / operations)
|
|
88
142
|
}
|
|
89
143
|
|
|
90
144
|
return {
|
|
91
145
|
samplesNs,
|
|
92
146
|
batchSize,
|
|
93
|
-
overheadNs: overheadNs /
|
|
94
|
-
heapDeltaBytes:
|
|
147
|
+
overheadNs: overheadNs / operations,
|
|
148
|
+
heapDeltaBytes:
|
|
149
|
+
sampling.heapDeltas.length === 0 ? null : median(sampling.heapDeltas.map(delta => delta / operations)),
|
|
150
|
+
metrics: null,
|
|
151
|
+
} satisfies Measurement
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
interface Run {
|
|
155
|
+
readonly elapsedNs: number
|
|
156
|
+
/** Work units in the run: stream items, or 1 for a scenario. */
|
|
157
|
+
readonly units: number
|
|
158
|
+
readonly metrics: Readonly<Record<string, number>>
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const runOnce = (operation: SingleShot, clock: Clock, memory: Memory, timer: Timer): Effect.Effect<Run, unknown> => {
|
|
162
|
+
if (operation._tag === 'Stream') {
|
|
163
|
+
return Effect.gen(function* () {
|
|
164
|
+
const stream = operation.stream()
|
|
165
|
+
const rss = memory.rss
|
|
166
|
+
let items = 0
|
|
167
|
+
let chunks = 0
|
|
168
|
+
let firstItemNs = Number.NaN
|
|
169
|
+
let peakRss = rss?.() ?? 0
|
|
170
|
+
const startedAt = timer.start()
|
|
171
|
+
yield* Stream.runForEachArray(stream, chunk =>
|
|
172
|
+
Effect.sync(() => {
|
|
173
|
+
if (items === 0) firstItemNs = clock.nowNs() - startedAt
|
|
174
|
+
items += chunk.length
|
|
175
|
+
blackhole = chunk
|
|
176
|
+
// Reading RSS is a syscall; sampling every 64th chunk keeps it out of the timing.
|
|
177
|
+
if (rss !== undefined && (chunks++ & 63) === 0) peakRss = Math.max(peakRss, rss())
|
|
178
|
+
})
|
|
179
|
+
)
|
|
180
|
+
const elapsedNs = timer.stop()
|
|
181
|
+
const metrics: Record<string, number> = { firstItemNs: items === 0 ? elapsedNs : firstItemNs, items }
|
|
182
|
+
if (rss !== undefined) metrics['peakRssBytes'] = Math.max(peakRss, rss())
|
|
183
|
+
return { elapsedNs, units: Math.max(items, 1), metrics }
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
return Effect.gen(function* () {
|
|
187
|
+
const input = operation.before === undefined ? undefined : yield* operation.before
|
|
188
|
+
const phases = new Map<string, number>()
|
|
189
|
+
const recorder = { path: '', record: (path: string, ns: number) => phases.set(path, (phases.get(path) ?? 0) + ns) }
|
|
190
|
+
timer.start()
|
|
191
|
+
const value = yield* Effect.provideService(operation.run(input), BenchPhases, recorder)
|
|
192
|
+
const elapsedNs = timer.stop()
|
|
193
|
+
if (operation.verify !== undefined && !operation.verify(value)) {
|
|
194
|
+
return yield* new CheckFailed({ message: `verify rejected the result: ${String(value)}` })
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
elapsedNs,
|
|
198
|
+
units: 1,
|
|
199
|
+
metrics: Object.fromEntries([...phases].map(([path, ns]) => [`phase:${path}Ns`, ns])),
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Streams and scenarios run whole once per sample; they are too slow to batch. */
|
|
205
|
+
const measureRuns = Effect.fn('effectBench/measureRuns')(function* (operation: SingleShot, options: MeasureOptions) {
|
|
206
|
+
const clock = yield* BenchClock
|
|
207
|
+
const memory = yield* BenchMemory
|
|
208
|
+
const maxSamples = options.maxSamples ?? 10_000
|
|
209
|
+
const plain = makeTimer(clock, undefined)
|
|
210
|
+
const sampling = makeTimer(clock, options.memory ? memory : undefined)
|
|
211
|
+
|
|
212
|
+
const warmupStartedAt = clock.nowNs()
|
|
213
|
+
do yield* runOnce(operation, clock, memory, plain)
|
|
214
|
+
while (clock.nowNs() - warmupStartedAt < options.warmupNs)
|
|
215
|
+
|
|
216
|
+
const samplesNs: Array<number> = []
|
|
217
|
+
const metrics: Record<string, Array<number>> = {}
|
|
218
|
+
const startedAt = clock.nowNs()
|
|
219
|
+
while (
|
|
220
|
+
samplesNs.length < maxSamples &&
|
|
221
|
+
(samplesNs.length < options.samples || clock.nowNs() - startedAt < options.minTimeNs)
|
|
222
|
+
) {
|
|
223
|
+
const run = yield* runOnce(operation, clock, memory, sampling)
|
|
224
|
+
samplesNs.push(run.elapsedNs / run.units)
|
|
225
|
+
for (const [key, value] of Object.entries(run.metrics)) (metrics[key] ??= []).push(value)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
samplesNs,
|
|
230
|
+
batchSize: 1,
|
|
231
|
+
overheadNs: 0,
|
|
232
|
+
heapDeltaBytes: sampling.heapDeltas.length === 0 ? null : median(sampling.heapDeltas),
|
|
233
|
+
metrics: Object.fromEntries(Object.entries(metrics).map(([key, values]) => [key, median(values)])),
|
|
95
234
|
} satisfies Measurement
|
|
96
235
|
})
|
|
236
|
+
|
|
237
|
+
export const measure = (operation: Operation, options: MeasureOptions): Effect.Effect<Measurement, unknown> =>
|
|
238
|
+
operation._tag === 'Stream' || operation._tag === 'Scenario'
|
|
239
|
+
? measureRuns(operation, options)
|
|
240
|
+
: measureBatched(operation, options)
|