overtake 1.3.2 → 2.0.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 +12 -15
- package/bin/overtake.js +1 -1
- package/build/executor.d.ts +8 -3
- package/build/index.d.ts +10 -11
- package/build/reporter.d.ts +10 -2
- package/build/runner.d.ts +1 -1
- package/build/types.d.ts +7 -7
- package/build/utils.d.ts +3 -17
- package/package.json +8 -26
- package/src/__tests__/assert-no-closure.ts +135 -0
- package/src/__tests__/benchmark-execute.ts +48 -0
- package/src/cli.ts +139 -144
- package/src/executor.ts +59 -24
- package/src/index.ts +135 -68
- package/src/reporter.ts +77 -125
- package/src/runner.ts +28 -25
- package/src/types.ts +9 -9
- package/src/utils.ts +62 -46
- package/src/worker.ts +13 -12
- package/tsconfig.json +3 -1
- package/build/cli.cjs +0 -179
- package/build/cli.cjs.map +0 -1
- package/build/cli.js +0 -134
- package/build/cli.js.map +0 -1
- package/build/executor.cjs +0 -116
- package/build/executor.cjs.map +0 -1
- package/build/executor.js +0 -106
- package/build/executor.js.map +0 -1
- package/build/gc-watcher.cjs +0 -30
- package/build/gc-watcher.cjs.map +0 -1
- package/build/gc-watcher.js +0 -20
- package/build/gc-watcher.js.map +0 -1
- package/build/index.cjs +0 -400
- package/build/index.cjs.map +0 -1
- package/build/index.js +0 -335
- package/build/index.js.map +0 -1
- package/build/reporter.cjs +0 -364
- package/build/reporter.cjs.map +0 -1
- package/build/reporter.js +0 -346
- package/build/reporter.js.map +0 -1
- package/build/runner.cjs +0 -528
- package/build/runner.cjs.map +0 -1
- package/build/runner.js +0 -518
- package/build/runner.js.map +0 -1
- package/build/types.cjs +0 -66
- package/build/types.cjs.map +0 -1
- package/build/types.js +0 -33
- package/build/types.js.map +0 -1
- package/build/utils.cjs +0 -121
- package/build/utils.cjs.map +0 -1
- package/build/utils.js +0 -85
- package/build/utils.js.map +0 -1
- package/build/worker.cjs +0 -158
- package/build/worker.cjs.map +0 -1
- package/build/worker.js +0 -113
- package/build/worker.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { cpus } from 'node:os';
|
|
2
2
|
import Progress from 'progress';
|
|
3
|
-
import { createExecutor, ExecutorOptions, ExecutorReport } from './executor.
|
|
4
|
-
import { MaybePromise, StepFn, SetupFn, TeardownFn, FeedFn, ReportType, ReportTypeList, DEFAULT_CYCLES, ProgressInfo } from './types.
|
|
3
|
+
import { createExecutor, type ExecutorOptions, type ExecutorReport } from './executor.ts';
|
|
4
|
+
import { type MaybePromise, type StepFn, type SetupFn, type TeardownFn, type FeedFn, type ReportType, type ReportTypeList, DEFAULT_CYCLES, type ProgressInfo } from './types.ts';
|
|
5
5
|
|
|
6
6
|
declare global {
|
|
7
7
|
const benchmark: typeof Benchmark.create;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export const DEFAULT_WORKERS = cpus().length;
|
|
10
|
+
export const DEFAULT_WORKERS = Math.max(1, Math.ceil(cpus().length / 4));
|
|
11
11
|
|
|
12
|
-
export const AsyncFunction = (async () => {}).constructor;
|
|
13
12
|
const BENCHMARK_URL = Symbol.for('overtake.benchmarkUrl');
|
|
14
13
|
|
|
15
14
|
export interface TargetReport<R extends ReportTypeList> {
|
|
@@ -30,14 +29,24 @@ export interface FeedReport<R extends ReportTypeList> {
|
|
|
30
29
|
export const DEFAULT_REPORT_TYPES = ['ops'] as const;
|
|
31
30
|
export type DefaultReportTypes = (typeof DEFAULT_REPORT_TYPES)[number];
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
const createExecutorErrorReport = <R extends ReportTypeList>(error: unknown): ExecutorReport<R> =>
|
|
33
|
+
({
|
|
34
|
+
count: 0,
|
|
35
|
+
heapUsedKB: 0,
|
|
36
|
+
dceWarning: false,
|
|
37
|
+
error: error instanceof Error ? error.message : String(error),
|
|
38
|
+
}) as ExecutorReport<R>;
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
export class MeasureContext<TContext, TInput> {
|
|
41
|
+
pre?: StepFn<TContext, TInput>;
|
|
42
|
+
post?: StepFn<TContext, TInput>;
|
|
43
|
+
title: string;
|
|
44
|
+
run: StepFn<TContext, TInput>;
|
|
45
|
+
|
|
46
|
+
constructor(title: string, run: StepFn<TContext, TInput>) {
|
|
47
|
+
this.title = title;
|
|
48
|
+
this.run = run;
|
|
49
|
+
}
|
|
41
50
|
}
|
|
42
51
|
|
|
43
52
|
export class Measure<TContext, TInput> {
|
|
@@ -58,13 +67,15 @@ export class Measure<TContext, TInput> {
|
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
export class TargetContext<TContext, TInput> {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
teardown?: TeardownFn<TContext>;
|
|
71
|
+
measures: MeasureContext<TContext, TInput>[] = [];
|
|
72
|
+
readonly title: string;
|
|
73
|
+
readonly setup?: SetupFn<MaybePromise<TContext>>;
|
|
74
|
+
|
|
75
|
+
constructor(title: string, setup?: SetupFn<MaybePromise<TContext>>) {
|
|
76
|
+
this.title = title;
|
|
77
|
+
this.setup = setup;
|
|
78
|
+
}
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
export class Target<TContext, TInput> {
|
|
@@ -87,10 +98,13 @@ export class Target<TContext, TInput> {
|
|
|
87
98
|
}
|
|
88
99
|
|
|
89
100
|
export class FeedContext<TInput> {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
) {
|
|
101
|
+
readonly title: string;
|
|
102
|
+
readonly fn?: FeedFn<TInput>;
|
|
103
|
+
|
|
104
|
+
constructor(title: string, fn?: FeedFn<TInput>) {
|
|
105
|
+
this.title = title;
|
|
106
|
+
this.fn = fn;
|
|
107
|
+
}
|
|
94
108
|
}
|
|
95
109
|
|
|
96
110
|
export class Benchmark<TInput> {
|
|
@@ -136,7 +150,7 @@ export class Benchmark<TInput> {
|
|
|
136
150
|
return new Target<TContext, TInput>(target);
|
|
137
151
|
}
|
|
138
152
|
|
|
139
|
-
async execute<R extends readonly ReportType[] = typeof DEFAULT_REPORT_TYPES>(options: ExecutorOptions<R
|
|
153
|
+
async execute<R extends readonly ReportType[] = typeof DEFAULT_REPORT_TYPES>(options: Partial<ExecutorOptions<R>> & { progress?: boolean } = {}): Promise<TargetReport<R>[]> {
|
|
140
154
|
const {
|
|
141
155
|
workers = DEFAULT_WORKERS,
|
|
142
156
|
warmupCycles = 20,
|
|
@@ -193,45 +207,59 @@ export class Benchmark<TInput> {
|
|
|
193
207
|
} as Required<ExecutorOptions<R>>);
|
|
194
208
|
|
|
195
209
|
const reports: TargetReport<R>[] = [];
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
210
|
+
const pendingReports: Promise<void>[] = [];
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const feedData = await Promise.all(this.#feeds.map(async (feed) => ({ title: feed.title, data: await feed.fn?.() })));
|
|
214
|
+
for (const target of this.#targets) {
|
|
215
|
+
const targetReport: TargetReport<R> = { target: target.title, measures: [] };
|
|
216
|
+
for (const measure of target.measures) {
|
|
217
|
+
const measureReport: MeasureReport<R> = { measure: measure.title, feeds: [] };
|
|
218
|
+
for (const feed of feedData) {
|
|
219
|
+
const id = `${target.title}/${measure.title}/${feed.title}`;
|
|
220
|
+
const feedReport: FeedReport<R> = {
|
|
221
|
+
feed: feed.title,
|
|
222
|
+
data: createExecutorErrorReport<R>('Benchmark did not produce a report'),
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
measureReport.feeds.push(feedReport);
|
|
226
|
+
pendingReports.push(
|
|
227
|
+
(async () => {
|
|
228
|
+
try {
|
|
229
|
+
feedReport.data = await executor.pushAsync<ExecutorReport<R>>({
|
|
230
|
+
id,
|
|
231
|
+
setup: target.setup,
|
|
232
|
+
teardown: target.teardown,
|
|
233
|
+
pre: measure.pre,
|
|
234
|
+
run: measure.run,
|
|
235
|
+
post: measure.post,
|
|
236
|
+
data: feed.data,
|
|
237
|
+
});
|
|
238
|
+
} catch (error) {
|
|
239
|
+
feedReport.data = createExecutorErrorReport<R>(error);
|
|
240
|
+
} finally {
|
|
241
|
+
progressMap.delete(id);
|
|
242
|
+
completedBenchmarks++;
|
|
243
|
+
}
|
|
244
|
+
})(),
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
targetReport.measures.push(measureReport);
|
|
221
248
|
}
|
|
222
|
-
|
|
249
|
+
reports.push(targetReport);
|
|
223
250
|
}
|
|
224
|
-
reports.push(targetReport);
|
|
225
|
-
}
|
|
226
|
-
await executor.drain();
|
|
227
|
-
executor.kill();
|
|
228
251
|
|
|
229
|
-
|
|
230
|
-
bar.update(1, { label: 'done' });
|
|
231
|
-
bar.terminate();
|
|
232
|
-
}
|
|
252
|
+
await Promise.all(pendingReports);
|
|
233
253
|
|
|
234
|
-
|
|
254
|
+
if (bar) {
|
|
255
|
+
bar.update(1, { label: 'done' });
|
|
256
|
+
bar.terminate();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return reports;
|
|
260
|
+
} finally {
|
|
261
|
+
executor.kill();
|
|
262
|
+
}
|
|
235
263
|
}
|
|
236
264
|
}
|
|
237
265
|
|
|
@@ -240,7 +268,11 @@ export const printSimpleReports = <R extends ReportTypeList>(reports: TargetRepo
|
|
|
240
268
|
for (const { measure, feeds } of report.measures) {
|
|
241
269
|
console.group('\n', report.target, measure);
|
|
242
270
|
for (const { feed, data } of feeds) {
|
|
243
|
-
const { count, heapUsedKB, dceWarning, ...metrics } = data as Record<string, unknown>;
|
|
271
|
+
const { count, heapUsedKB, dceWarning, error: benchError, ...metrics } = data as Record<string, unknown>;
|
|
272
|
+
if (benchError) {
|
|
273
|
+
console.log(feed, `\x1b[31m[error: ${benchError}]\x1b[0m`);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
244
276
|
const output = Object.entries(metrics)
|
|
245
277
|
.map(([key, report]) => `${key}: ${(report as { toString(): string }).toString()}`)
|
|
246
278
|
.join('; ');
|
|
@@ -261,7 +293,12 @@ export const printTableReports = <R extends ReportTypeList>(reports: TargetRepor
|
|
|
261
293
|
console.log('\n', report.target, measure);
|
|
262
294
|
const table: Record<string, unknown> = {};
|
|
263
295
|
for (const { feed, data } of feeds) {
|
|
264
|
-
|
|
296
|
+
const { error: benchError } = data as Record<string, unknown>;
|
|
297
|
+
if (benchError) {
|
|
298
|
+
table[feed] = { error: benchError };
|
|
299
|
+
} else {
|
|
300
|
+
table[feed] = Object.fromEntries(Object.entries(data).map(([key, report]) => [key, report.toString()]));
|
|
301
|
+
}
|
|
265
302
|
}
|
|
266
303
|
console.table(table);
|
|
267
304
|
}
|
|
@@ -274,7 +311,12 @@ export const printJSONReports = <R extends ReportTypeList>(reports: TargetReport
|
|
|
274
311
|
for (const { measure, feeds } of report.measures) {
|
|
275
312
|
const row = {} as Record<string, Record<string, string>>;
|
|
276
313
|
for (const { feed, data } of feeds) {
|
|
277
|
-
|
|
314
|
+
const { error: benchError } = data as Record<string, unknown>;
|
|
315
|
+
if (benchError) {
|
|
316
|
+
row[feed] = { error: String(benchError) };
|
|
317
|
+
} else {
|
|
318
|
+
row[feed] = Object.fromEntries(Object.entries(data).map(([key, report]) => [key, report.toString()]));
|
|
319
|
+
}
|
|
278
320
|
}
|
|
279
321
|
output[`${report.target} ${measure}`] = row;
|
|
280
322
|
}
|
|
@@ -288,7 +330,14 @@ export const printMarkdownReports = <R extends ReportTypeList>(reports: TargetRe
|
|
|
288
330
|
console.log(`\n## ${report.target} - ${measure}\n`);
|
|
289
331
|
if (feeds.length === 0) continue;
|
|
290
332
|
|
|
291
|
-
const
|
|
333
|
+
const firstValid = feeds.find((f) => !(f.data as Record<string, unknown>).error);
|
|
334
|
+
if (!firstValid) {
|
|
335
|
+
for (const { feed, data } of feeds) {
|
|
336
|
+
console.log(`| ${feed} | error: ${(data as Record<string, unknown>).error} |`);
|
|
337
|
+
}
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const keys = Object.keys(firstValid.data).filter((k) => k !== 'count' && k !== 'error');
|
|
292
341
|
const header = ['Feed', ...keys].join(' | ');
|
|
293
342
|
const separator = ['---', ...keys.map(() => '---')].join(' | ');
|
|
294
343
|
|
|
@@ -296,6 +345,10 @@ export const printMarkdownReports = <R extends ReportTypeList>(reports: TargetRe
|
|
|
296
345
|
console.log(`| ${separator} |`);
|
|
297
346
|
|
|
298
347
|
for (const { feed, data } of feeds) {
|
|
348
|
+
if ((data as Record<string, unknown>).error) {
|
|
349
|
+
console.log(`| ${feed} | error: ${(data as Record<string, unknown>).error} |`);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
299
352
|
const values = keys.map((k) => (data as Record<string, { toString(): string }>)[k]?.toString() ?? '-');
|
|
300
353
|
console.log(`| ${[feed, ...values].join(' | ')} |`);
|
|
301
354
|
}
|
|
@@ -309,18 +362,26 @@ export const printHistogramReports = <R extends ReportTypeList>(reports: TargetR
|
|
|
309
362
|
console.log(`\n${report.target} - ${measure}\n`);
|
|
310
363
|
|
|
311
364
|
const opsKey = 'ops';
|
|
312
|
-
const values = feeds.map((f) =>
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
365
|
+
const values = feeds.map((f) => {
|
|
366
|
+
const { error: benchError } = f.data as Record<string, unknown>;
|
|
367
|
+
return {
|
|
368
|
+
feed: f.feed,
|
|
369
|
+
value: benchError ? 0 : ((f.data as Record<string, { valueOf(): number }>)[opsKey]?.valueOf() ?? 0),
|
|
370
|
+
error: benchError as string | undefined,
|
|
371
|
+
};
|
|
372
|
+
});
|
|
316
373
|
|
|
317
374
|
const maxValue = Math.max(...values.map((v) => v.value));
|
|
318
375
|
const maxLabelLen = Math.max(...values.map((v) => v.feed.length));
|
|
319
376
|
|
|
320
|
-
for (const { feed, value } of values) {
|
|
377
|
+
for (const { feed, value, error } of values) {
|
|
378
|
+
const label = feed.padEnd(maxLabelLen);
|
|
379
|
+
if (error) {
|
|
380
|
+
console.log(` ${label} | \x1b[31m[error: ${error}]\x1b[0m`);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
321
383
|
const barLen = maxValue > 0 ? Math.round((value / maxValue) * width) : 0;
|
|
322
384
|
const bar = '\u2588'.repeat(barLen);
|
|
323
|
-
const label = feed.padEnd(maxLabelLen);
|
|
324
385
|
const formatted = value.toLocaleString('en-US', { maximumFractionDigits: 2 });
|
|
325
386
|
console.log(` ${label} | ${bar} ${formatted} ops/s`);
|
|
326
387
|
}
|
|
@@ -339,6 +400,7 @@ export const reportsToBaseline = <R extends ReportTypeList>(reports: TargetRepor
|
|
|
339
400
|
for (const report of reports) {
|
|
340
401
|
for (const { measure, feeds } of report.measures) {
|
|
341
402
|
for (const { feed, data } of feeds) {
|
|
403
|
+
if ((data as Record<string, unknown>).error) continue;
|
|
342
404
|
const key = `${report.target}/${measure}/${feed}`;
|
|
343
405
|
results[key] = {};
|
|
344
406
|
for (const [metric, value] of Object.entries(data)) {
|
|
@@ -367,6 +429,11 @@ export const printComparisonReports = <R extends ReportTypeList>(reports: Target
|
|
|
367
429
|
|
|
368
430
|
console.log(` ${feed}:`);
|
|
369
431
|
|
|
432
|
+
if ((data as Record<string, unknown>).error) {
|
|
433
|
+
console.log(` \x1b[31m[error: ${(data as Record<string, unknown>).error}]\x1b[0m`);
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
|
|
370
437
|
for (const [metric, value] of Object.entries(data)) {
|
|
371
438
|
if (metric === 'count') continue;
|
|
372
439
|
const current = (value as { valueOf(): number }).valueOf();
|
package/src/reporter.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { div, max, divs } from './utils.
|
|
2
|
-
import { ReportType, DURATION_SCALE
|
|
1
|
+
import { div, max, divs, isqrt } from './utils.ts';
|
|
2
|
+
import { type ReportType, DURATION_SCALE } from './types.ts';
|
|
3
3
|
|
|
4
4
|
const units = [
|
|
5
5
|
{ unit: 'ns', factor: 1 },
|
|
@@ -18,12 +18,17 @@ function smartFixed(n: number): string {
|
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
20
|
export class Report {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
) {
|
|
21
|
+
readonly type: ReportType;
|
|
22
|
+
readonly value: bigint;
|
|
23
|
+
readonly uncertainty: number;
|
|
24
|
+
readonly scale: bigint;
|
|
25
|
+
|
|
26
|
+
constructor(type: ReportType, value: bigint, uncertainty: number = 0, scale: bigint = 1n) {
|
|
27
|
+
this.type = type;
|
|
28
|
+
this.value = value;
|
|
29
|
+
this.uncertainty = uncertainty;
|
|
30
|
+
this.scale = scale;
|
|
31
|
+
}
|
|
27
32
|
valueOf() {
|
|
28
33
|
return Number(div(this.value, this.scale));
|
|
29
34
|
}
|
|
@@ -70,11 +75,32 @@ export class Report {
|
|
|
70
75
|
}
|
|
71
76
|
}
|
|
72
77
|
|
|
73
|
-
|
|
78
|
+
const SQRT_SCALE = 1_000_000n;
|
|
79
|
+
const SQRT_SCALE_SQ = SQRT_SCALE * SQRT_SCALE;
|
|
80
|
+
const Z95_NUM = 196n;
|
|
81
|
+
const Z95_DENOM = 100n;
|
|
82
|
+
|
|
83
|
+
type Stats = { sum: bigint; mean: bigint; ssd: bigint; n: bigint };
|
|
84
|
+
|
|
85
|
+
export const computeStats = (durations: BigUint64Array): Stats => {
|
|
86
|
+
let sum = 0n;
|
|
87
|
+
for (const d of durations) sum += d;
|
|
88
|
+
const n = BigInt(durations.length);
|
|
89
|
+
const mean = sum / n;
|
|
90
|
+
let ssd = 0n;
|
|
91
|
+
for (const d of durations) {
|
|
92
|
+
const diff = d - mean;
|
|
93
|
+
ssd += diff * diff;
|
|
94
|
+
}
|
|
95
|
+
return { sum, mean, ssd, n };
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const createReport = (durations: BigUint64Array, type: ReportType, stats?: Stats): Report => {
|
|
74
99
|
const n = durations.length;
|
|
75
100
|
if (n === 0) {
|
|
76
101
|
return new Report(type, 0n);
|
|
77
102
|
}
|
|
103
|
+
const st = stats ?? computeStats(durations);
|
|
78
104
|
switch (type) {
|
|
79
105
|
case 'min': {
|
|
80
106
|
return new Report(type, durations[0], 0, DURATION_SCALE);
|
|
@@ -112,11 +138,7 @@ export const createReport = (durations: BigUint64Array, type: ReportType): Repor
|
|
|
112
138
|
}
|
|
113
139
|
|
|
114
140
|
case 'ops': {
|
|
115
|
-
|
|
116
|
-
for (const duration of durations) {
|
|
117
|
-
sum += duration;
|
|
118
|
-
}
|
|
119
|
-
const avgScaled = sum / BigInt(n);
|
|
141
|
+
const { mean: avgScaled, ssd, n: nBig } = st;
|
|
120
142
|
const nsPerSecScaled = 1_000_000_000n * DURATION_SCALE;
|
|
121
143
|
const raw = Number(nsPerSecScaled) / Number(avgScaled);
|
|
122
144
|
const extra = raw < 1 ? Math.ceil(-Math.log10(raw)) : 0;
|
|
@@ -126,111 +148,61 @@ export const createReport = (durations: BigUint64Array, type: ReportType): Repor
|
|
|
126
148
|
const scale = 10n ** BigInt(exp);
|
|
127
149
|
|
|
128
150
|
const value = avgScaled > 0n ? (nsPerSecScaled * scale) / avgScaled : 0n;
|
|
129
|
-
|
|
130
|
-
|
|
151
|
+
let uncertainty = 0;
|
|
152
|
+
if (n >= 2 && avgScaled > 0n) {
|
|
153
|
+
const RME_PRECISION = 1_000_000n;
|
|
154
|
+
const semOverMeanSqScaled = (ssd * RME_PRECISION * RME_PRECISION) / (BigInt(n - 1) * nBig * avgScaled * avgScaled);
|
|
155
|
+
const semOverMeanScaled = isqrt(semOverMeanSqScaled);
|
|
156
|
+
uncertainty = Number(Z95_NUM * semOverMeanScaled) / Number(RME_PRECISION);
|
|
157
|
+
}
|
|
131
158
|
return new Report(type, value, uncertainty, scale);
|
|
132
159
|
}
|
|
133
160
|
case 'mean': {
|
|
134
|
-
|
|
135
|
-
for (const duration of durations) {
|
|
136
|
-
sum += duration;
|
|
137
|
-
}
|
|
161
|
+
const { sum } = st;
|
|
138
162
|
const value = divs(sum, BigInt(n), 1n);
|
|
139
163
|
return new Report(type, value, 0, DURATION_SCALE);
|
|
140
164
|
}
|
|
141
165
|
|
|
142
166
|
case 'variance': {
|
|
143
167
|
if (n < 2) return new Report(type, 0n, 0, DURATION_SCALE * DURATION_SCALE);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
sum += duration;
|
|
147
|
-
}
|
|
148
|
-
const mean = sum / BigInt(n);
|
|
149
|
-
let sumSquaredDiff = 0n;
|
|
150
|
-
for (const duration of durations) {
|
|
151
|
-
const diff = duration - mean;
|
|
152
|
-
sumSquaredDiff += diff * diff;
|
|
153
|
-
}
|
|
154
|
-
const variance = sumSquaredDiff / BigInt(n - 1);
|
|
168
|
+
const { ssd } = st;
|
|
169
|
+
const variance = ssd / BigInt(n - 1);
|
|
155
170
|
return new Report(type, variance, 0, DURATION_SCALE * DURATION_SCALE);
|
|
156
171
|
}
|
|
157
172
|
|
|
158
173
|
case 'sd': {
|
|
159
174
|
if (n < 2) return new Report(type, 0n, 0, DURATION_SCALE);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const mean = sum / BigInt(n);
|
|
165
|
-
let sumSquaredDiff = 0n;
|
|
166
|
-
for (const duration of durations) {
|
|
167
|
-
const diff = duration - mean;
|
|
168
|
-
sumSquaredDiff += diff * diff;
|
|
169
|
-
}
|
|
170
|
-
const variance = Number(sumSquaredDiff) / (n - 1);
|
|
171
|
-
const sd = Math.sqrt(variance);
|
|
172
|
-
const sdScaled = BigInt(Math.round(sd));
|
|
173
|
-
return new Report(type, sdScaled, 0, DURATION_SCALE);
|
|
175
|
+
const { ssd } = st;
|
|
176
|
+
const scaledVariance = (ssd * SQRT_SCALE_SQ) / BigInt(n - 1);
|
|
177
|
+
const sdScaled = isqrt(scaledVariance);
|
|
178
|
+
return new Report(type, sdScaled, 0, DURATION_SCALE * SQRT_SCALE);
|
|
174
179
|
}
|
|
175
180
|
|
|
176
181
|
case 'sem': {
|
|
177
182
|
if (n < 2) return new Report(type, 0n, 0, DURATION_SCALE);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const mean = sum / BigInt(n);
|
|
183
|
-
let sumSquaredDiff = 0n;
|
|
184
|
-
for (const duration of durations) {
|
|
185
|
-
const diff = duration - mean;
|
|
186
|
-
sumSquaredDiff += diff * diff;
|
|
187
|
-
}
|
|
188
|
-
const variance = Number(sumSquaredDiff) / (n - 1);
|
|
189
|
-
const sd = Math.sqrt(variance);
|
|
190
|
-
const sem = sd / Math.sqrt(n);
|
|
191
|
-
const semScaled = BigInt(Math.round(sem));
|
|
192
|
-
return new Report(type, semScaled, 0, DURATION_SCALE);
|
|
183
|
+
const { ssd, n: nBig } = st;
|
|
184
|
+
const semSqScaled = (ssd * SQRT_SCALE_SQ) / (BigInt(n - 1) * nBig);
|
|
185
|
+
const semScaled = isqrt(semSqScaled);
|
|
186
|
+
return new Report(type, semScaled, 0, DURATION_SCALE * SQRT_SCALE);
|
|
193
187
|
}
|
|
194
188
|
|
|
195
189
|
case 'moe': {
|
|
196
190
|
if (n < 2) return new Report(type, 0n, 0, DURATION_SCALE);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
let sumSquaredDiff = 0n;
|
|
203
|
-
for (const duration of durations) {
|
|
204
|
-
const diff = duration - mean;
|
|
205
|
-
sumSquaredDiff += diff * diff;
|
|
206
|
-
}
|
|
207
|
-
const variance = Number(sumSquaredDiff) / (n - 1);
|
|
208
|
-
const sd = Math.sqrt(variance);
|
|
209
|
-
const sem = sd / Math.sqrt(n);
|
|
210
|
-
const moe = Z95 * sem;
|
|
211
|
-
const moeScaled = BigInt(Math.round(moe));
|
|
212
|
-
return new Report(type, moeScaled, 0, DURATION_SCALE);
|
|
191
|
+
const { ssd, n: nBig } = st;
|
|
192
|
+
const semSqScaled = (ssd * SQRT_SCALE_SQ) / (BigInt(n - 1) * nBig);
|
|
193
|
+
const semScaled = isqrt(semSqScaled);
|
|
194
|
+
const moeScaled = (Z95_NUM * semScaled) / Z95_DENOM;
|
|
195
|
+
return new Report(type, moeScaled, 0, DURATION_SCALE * SQRT_SCALE);
|
|
213
196
|
}
|
|
214
197
|
|
|
215
198
|
case 'rme': {
|
|
216
199
|
if (n < 2) return new Report(type, 0n);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
let sumSquaredDiff = 0;
|
|
224
|
-
for (const duration of durations) {
|
|
225
|
-
const diff = Number(duration) - mean;
|
|
226
|
-
sumSquaredDiff += diff * diff;
|
|
227
|
-
}
|
|
228
|
-
const variance = sumSquaredDiff / (n - 1);
|
|
229
|
-
const sd = Math.sqrt(variance);
|
|
230
|
-
const sem = sd / Math.sqrt(n);
|
|
231
|
-
const moe = Z95 * sem;
|
|
232
|
-
const rme = (moe / mean) * 100;
|
|
233
|
-
const rmeScaled = BigInt(Math.round(rme * 100));
|
|
200
|
+
const { mean, ssd, n: nBig } = st;
|
|
201
|
+
if (mean === 0n) return new Report(type, 0n);
|
|
202
|
+
const RME_PRECISION = 1_000_000n;
|
|
203
|
+
const semOverMeanSqScaled = (ssd * RME_PRECISION * RME_PRECISION) / (BigInt(n - 1) * nBig * mean * mean);
|
|
204
|
+
const semOverMeanScaled = isqrt(semOverMeanSqScaled);
|
|
205
|
+
const rmeScaled = (Z95_NUM * semOverMeanScaled * 100n) / RME_PRECISION;
|
|
234
206
|
return new Report(type, rmeScaled, 0, 100n);
|
|
235
207
|
}
|
|
236
208
|
|
|
@@ -259,42 +231,22 @@ export const createReport = (durations: BigUint64Array, type: ReportType): Repor
|
|
|
259
231
|
|
|
260
232
|
case 'ci_lower': {
|
|
261
233
|
if (n < 2) return new Report(type, 0n, 0, DURATION_SCALE);
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
for (const duration of durations) {
|
|
269
|
-
const diff = Number(duration) - mean;
|
|
270
|
-
sumSquaredDiff += diff * diff;
|
|
271
|
-
}
|
|
272
|
-
const variance = sumSquaredDiff / (n - 1);
|
|
273
|
-
const sd = Math.sqrt(variance);
|
|
274
|
-
const sem = sd / Math.sqrt(n);
|
|
275
|
-
const moe = Z95 * sem;
|
|
276
|
-
const ciLower = Math.max(0, mean - moe);
|
|
277
|
-
return new Report(type, BigInt(Math.round(ciLower)), 0, DURATION_SCALE);
|
|
234
|
+
const { mean, ssd, n: nBig } = st;
|
|
235
|
+
const semSqScaled = (ssd * SQRT_SCALE_SQ) / (BigInt(n - 1) * nBig);
|
|
236
|
+
const semScaled = isqrt(semSqScaled);
|
|
237
|
+
const moeScaled = (Z95_NUM * semScaled) / Z95_DENOM;
|
|
238
|
+
const ciLowerScaled = mean * SQRT_SCALE - moeScaled;
|
|
239
|
+
return new Report(type, ciLowerScaled > 0n ? ciLowerScaled : 0n, 0, DURATION_SCALE * SQRT_SCALE);
|
|
278
240
|
}
|
|
279
241
|
|
|
280
242
|
case 'ci_upper': {
|
|
281
243
|
if (n < 2) return new Report(type, 0n, 0, DURATION_SCALE);
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
for (const duration of durations) {
|
|
289
|
-
const diff = Number(duration) - mean;
|
|
290
|
-
sumSquaredDiff += diff * diff;
|
|
291
|
-
}
|
|
292
|
-
const variance = sumSquaredDiff / (n - 1);
|
|
293
|
-
const sd = Math.sqrt(variance);
|
|
294
|
-
const sem = sd / Math.sqrt(n);
|
|
295
|
-
const moe = Z95 * sem;
|
|
296
|
-
const ciUpper = mean + moe;
|
|
297
|
-
return new Report(type, BigInt(Math.round(ciUpper)), 0, DURATION_SCALE);
|
|
244
|
+
const { mean, ssd, n: nBig } = st;
|
|
245
|
+
const semSqScaled = (ssd * SQRT_SCALE_SQ) / (BigInt(n - 1) * nBig);
|
|
246
|
+
const semScaled = isqrt(semSqScaled);
|
|
247
|
+
const moeScaled = (Z95_NUM * semScaled) / Z95_DENOM;
|
|
248
|
+
const ciUpperScaled = mean * SQRT_SCALE + moeScaled;
|
|
249
|
+
return new Report(type, ciUpperScaled, 0, DURATION_SCALE * SQRT_SCALE);
|
|
298
250
|
}
|
|
299
251
|
|
|
300
252
|
default: {
|