@indutny/bencher 1.1.5 → 1.2.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 +1 -1
- package/dist/bin/bencher.js +60 -36
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/bin/bencher.js
CHANGED
|
@@ -32,8 +32,11 @@ const promises_1 = require("fs/promises");
|
|
|
32
32
|
const yargs_1 = __importDefault(require("yargs"));
|
|
33
33
|
const helpers_1 = require("yargs/helpers");
|
|
34
34
|
// ANSI colors
|
|
35
|
-
const BOLD =
|
|
36
|
-
const ITALIC =
|
|
35
|
+
const BOLD = '\x1b[1m';
|
|
36
|
+
const ITALIC = '\x1b[3m';
|
|
37
|
+
const RESET = '\x1b[m';
|
|
38
|
+
const GREY = '\x1b[90m';
|
|
39
|
+
const RED = '\x1b[31m';
|
|
37
40
|
// Go back to previous line, clear the line
|
|
38
41
|
const PREV_LINE = '\x1b[F\x1b[K';
|
|
39
42
|
// From https://github.com/mayuki/Kurukuru
|
|
@@ -61,19 +64,24 @@ async function main() {
|
|
|
61
64
|
.option('w', {
|
|
62
65
|
alias: 'sweep-width',
|
|
63
66
|
type: 'number',
|
|
64
|
-
default:
|
|
67
|
+
default: 5,
|
|
65
68
|
describe: 'width of iteration sweep',
|
|
66
69
|
})
|
|
67
|
-
.option('warm-up-
|
|
70
|
+
.option('warm-up-duration', {
|
|
68
71
|
type: 'number',
|
|
69
|
-
default:
|
|
70
|
-
describe: '
|
|
72
|
+
default: 0.5,
|
|
73
|
+
describe: 'duration of warm up',
|
|
74
|
+
})
|
|
75
|
+
.option('ignore-outliers', {
|
|
76
|
+
alias: 'q',
|
|
77
|
+
type: 'boolean',
|
|
78
|
+
describe: "don't report severe outliers",
|
|
71
79
|
}).argv;
|
|
72
80
|
const modules = await Promise.all(argv._.map(async (file) => {
|
|
73
81
|
var _a;
|
|
74
82
|
const path = await (0, promises_1.realpath)(String(file));
|
|
75
83
|
const m = await (_a = path, Promise.resolve().then(() => __importStar(require(_a))));
|
|
76
|
-
const { duration = argv['duration'], sweepWidth = argv['sweepWidth'], samples = argv['samples'],
|
|
84
|
+
const { duration = argv['duration'], sweepWidth = argv['sweepWidth'], samples = argv['samples'], warmUpDuration = argv['warmUpDuration'], ignoreOutliers = argv['ignoreOutliers'], } = m.options ?? {};
|
|
77
85
|
if (duration <= 0) {
|
|
78
86
|
throw new Error(`${file}: options.duration must be positive`);
|
|
79
87
|
}
|
|
@@ -86,8 +94,8 @@ async function main() {
|
|
|
86
94
|
if (samples / sweepWidth < 2) {
|
|
87
95
|
throw new Error(`${file}: options.samples must be greater than 2 * sweepWidth`);
|
|
88
96
|
}
|
|
89
|
-
if (
|
|
90
|
-
throw new Error(`${file}: options.
|
|
97
|
+
if (warmUpDuration <= 0) {
|
|
98
|
+
throw new Error(`${file}: options.warmUpDuration must be positive`);
|
|
91
99
|
}
|
|
92
100
|
return {
|
|
93
101
|
name: m.name ?? String(file),
|
|
@@ -95,14 +103,15 @@ async function main() {
|
|
|
95
103
|
duration,
|
|
96
104
|
sweepWidth,
|
|
97
105
|
samples,
|
|
98
|
-
|
|
106
|
+
warmUpDuration,
|
|
107
|
+
ignoreOutliers,
|
|
99
108
|
},
|
|
100
109
|
default: m.default,
|
|
101
110
|
};
|
|
102
111
|
}));
|
|
103
112
|
const maxNameLength = modules.reduce((len, { name }) => Math.max(len, name.length), 0);
|
|
104
113
|
for (const m of modules) {
|
|
105
|
-
const paddedName =
|
|
114
|
+
const paddedName = BOLD + m.name + RESET + ':' + ' '.repeat(maxNameLength - m.name.length);
|
|
106
115
|
// Just to reserve the line
|
|
107
116
|
(0, fs_1.writeSync)(process.stdout.fd, '\n');
|
|
108
117
|
let ticks = 0;
|
|
@@ -111,12 +120,20 @@ async function main() {
|
|
|
111
120
|
ticks = (ticks + 1) % SPINNER.length;
|
|
112
121
|
};
|
|
113
122
|
onTick();
|
|
114
|
-
const { ops, maxError,
|
|
123
|
+
const { ops, maxError, outliers, severeOutliers } = run(m, {
|
|
115
124
|
onTick,
|
|
116
125
|
});
|
|
117
|
-
const stats =
|
|
118
|
-
|
|
119
|
-
|
|
126
|
+
const stats = [
|
|
127
|
+
`±${nice(maxError)}`,
|
|
128
|
+
`p=${P_VALUE}`,
|
|
129
|
+
`o=${outliers + severeOutliers}/${m.options.samples}`,
|
|
130
|
+
];
|
|
131
|
+
let warning = '';
|
|
132
|
+
if (!m.options.ignoreOutliers && severeOutliers !== 0) {
|
|
133
|
+
warning = `${RESET}${RED} severe outliers=${severeOutliers}`;
|
|
134
|
+
}
|
|
135
|
+
(0, fs_1.writeSync)(process.stdout.fd, `${PREV_LINE}${paddedName} ${nice(ops)} ops/sec ` +
|
|
136
|
+
`${GREY + ITALIC}(${stats.join(', ')})${warning}${RESET}\n`);
|
|
120
137
|
}
|
|
121
138
|
}
|
|
122
139
|
function run(m, { onTick }) {
|
|
@@ -135,21 +152,23 @@ function run(m, { onTick }) {
|
|
|
135
152
|
nextTick += tickEvery;
|
|
136
153
|
}
|
|
137
154
|
}
|
|
138
|
-
const { beta, confidence, outliers } = regress(m, samples);
|
|
155
|
+
const { beta, confidence, outliers, severeOutliers } = regress(m, samples);
|
|
139
156
|
const ops = 1 / beta;
|
|
140
157
|
const lowOps = 1 / (beta + confidence);
|
|
141
158
|
const highOps = 1 / (beta - confidence);
|
|
142
159
|
const maxError = Math.max(highOps - ops, ops - lowOps);
|
|
143
|
-
const usedSamples = samples.length - outliers;
|
|
144
160
|
return {
|
|
145
161
|
ops,
|
|
146
162
|
maxError,
|
|
147
|
-
|
|
163
|
+
outliers,
|
|
164
|
+
severeOutliers,
|
|
148
165
|
};
|
|
149
166
|
}
|
|
150
167
|
function warmUp(m) {
|
|
151
168
|
// Initial warm-up
|
|
152
|
-
|
|
169
|
+
const coldDuration = measure(m, 1);
|
|
170
|
+
const warmUpIterations = m.options.warmUpDuration / coldDuration;
|
|
171
|
+
for (let i = 0; i < warmUpIterations; i++) {
|
|
153
172
|
m.default();
|
|
154
173
|
}
|
|
155
174
|
// Compute max duration per base sample
|
|
@@ -192,35 +211,42 @@ function regress(m, samples) {
|
|
|
192
211
|
}
|
|
193
212
|
bin.push(duration);
|
|
194
213
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
214
|
+
let outliers = 0;
|
|
215
|
+
let severeOutliers = 0;
|
|
216
|
+
// Within each iteration bin identify the outliers for reporting purposes.
|
|
217
|
+
for (const [, durations] of bins) {
|
|
198
218
|
durations.sort();
|
|
199
219
|
const p25 = durations[Math.floor(durations.length * 0.25)] ?? -Infinity;
|
|
200
220
|
const p75 = durations[Math.ceil(durations.length * 0.75)] ?? +Infinity;
|
|
201
221
|
const iqr = p75 - p25;
|
|
202
222
|
const outlierLow = p25 - iqr * 1.5;
|
|
203
223
|
const outlierHigh = p75 + iqr * 1.5;
|
|
224
|
+
const badOutlierLow = p25 - iqr * 3;
|
|
225
|
+
const badOutlierHigh = p75 + iqr * 3;
|
|
204
226
|
// Tukey's method
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
227
|
+
for (const d of durations) {
|
|
228
|
+
if (d < badOutlierLow || d > badOutlierHigh) {
|
|
229
|
+
severeOutliers++;
|
|
230
|
+
}
|
|
231
|
+
else if (d < outlierLow || d > outlierHigh) {
|
|
232
|
+
outliers++;
|
|
233
|
+
}
|
|
208
234
|
}
|
|
209
235
|
}
|
|
210
|
-
if (
|
|
236
|
+
if (samples.length < 2) {
|
|
211
237
|
throw new Error(`${m.name}: low sample count`);
|
|
212
238
|
}
|
|
213
239
|
let meanDuration = 0;
|
|
214
240
|
let meanIterations = 0;
|
|
215
|
-
for (const { duration, iterations } of
|
|
241
|
+
for (const { duration, iterations } of samples) {
|
|
216
242
|
meanDuration += duration;
|
|
217
243
|
meanIterations += iterations;
|
|
218
244
|
}
|
|
219
|
-
meanDuration /=
|
|
220
|
-
meanIterations /=
|
|
245
|
+
meanDuration /= samples.length;
|
|
246
|
+
meanIterations /= samples.length;
|
|
221
247
|
let betaNum = 0;
|
|
222
248
|
let betaDenom = 0;
|
|
223
|
-
for (const { duration, iterations } of
|
|
249
|
+
for (const { duration, iterations } of samples) {
|
|
224
250
|
betaNum += (duration - meanDuration) * (iterations - meanIterations);
|
|
225
251
|
betaDenom += (iterations - meanIterations) ** 2;
|
|
226
252
|
}
|
|
@@ -229,22 +255,20 @@ function regress(m, samples) {
|
|
|
229
255
|
// Intercept
|
|
230
256
|
const alpha = meanDuration - beta * meanIterations;
|
|
231
257
|
let stdError = 0;
|
|
232
|
-
for (const { duration, iterations } of
|
|
258
|
+
for (const { duration, iterations } of samples) {
|
|
233
259
|
stdError += (duration - alpha - beta * iterations) ** 2;
|
|
234
260
|
}
|
|
235
|
-
stdError /=
|
|
261
|
+
stdError /= samples.length - 2;
|
|
236
262
|
stdError /= betaDenom;
|
|
237
263
|
stdError = Math.sqrt(stdError);
|
|
238
264
|
return {
|
|
239
265
|
alpha,
|
|
240
266
|
beta,
|
|
241
267
|
confidence: STUDENT_T * stdError,
|
|
242
|
-
outliers
|
|
268
|
+
outliers,
|
|
269
|
+
severeOutliers,
|
|
243
270
|
};
|
|
244
271
|
}
|
|
245
|
-
function style(text, code) {
|
|
246
|
-
return `\x1b[${code}m${text}\x1b[m`;
|
|
247
|
-
}
|
|
248
272
|
function nice(n) {
|
|
249
273
|
let result = n.toFixed(1);
|
|
250
274
|
for (let i = result.length - 5; i > 0; i -= 3) {
|