Skip to content

Commit 4600e1d

Browse files
Benchmark: Inline code from benchmark.js (#2019)
1 parent 758d08a commit 4600e1d

File tree

4 files changed

+155
-55
lines changed

4 files changed

+155
-55
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
"@babel/preset-env": "7.4.5",
5050
"@babel/register": "7.4.4",
5151
"babel-eslint": "10.0.2",
52-
"benchmark": "2.1.4",
5352
"chai": "4.2.0",
5453
"eslint": "5.16.0",
5554
"eslint-plugin-flowtype": "3.11.1",

resources/benchmark.js

Lines changed: 123 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
const os = require('os');
66
const fs = require('fs');
77
const path = require('path');
8-
const { Benchmark } = require('benchmark');
8+
const assert = require('assert');
99

10+
const { red, green, yellow, cyan, grey } = require('./colors');
1011
const {
1112
exec,
1213
copyFile,
@@ -16,8 +17,17 @@ const {
1617
readdirRecursive,
1718
} = require('./utils');
1819

20+
const NS_PER_SEC = 1e9;
1921
const LOCAL = 'local';
2022

23+
const minTime = 0.05 * NS_PER_SEC;
24+
// The maximum time a benchmark is allowed to run before finishing.
25+
const maxTime = 5 * NS_PER_SEC;
26+
// The minimum sample size required to perform statistical analysis.
27+
const minSamples = 15;
28+
// The default number of times to execute a test on a benchmark's first cycle.
29+
const initCount = 10;
30+
2131
function LOCAL_DIR(...paths) {
2232
return path.join(__dirname, '..', ...paths);
2333
}
@@ -93,43 +103,132 @@ function runBenchmark(benchmark, environments) {
93103
const benches = environments.map(environment => {
94104
const module = require(path.join(environment.distPath, benchmark));
95105
benchmarkName = module.name;
96-
return new Benchmark(environment.revision, module.measure);
106+
return {
107+
name: environment.revision,
108+
fn: module.measure,
109+
};
97110
});
98111

99112
console.log('⏱️ ' + benchmarkName);
113+
const results = [];
100114
for (let i = 0; i < benches.length; ++i) {
101-
benches[i].run({ async: false });
102-
process.stdout.write(' ' + cyan(i + 1) + ' tests completed.\u000D');
115+
const { name, fn } = benches[i];
116+
try {
117+
const samples = collectSamples(fn);
118+
results.push({ name, samples, ...computeStats(samples) });
119+
process.stdout.write(' ' + cyan(i + 1) + ' tests completed.\u000D');
120+
} catch (error) {
121+
console.log(' ' + name + ': ' + red(String(error)));
122+
}
103123
}
104124
console.log('\n');
105125

106-
beautifyBenchmark(benches);
126+
beautifyBenchmark(results);
107127
console.log('');
108128
}
109129

110-
function beautifyBenchmark(results) {
111-
const benches = results.map(result => ({
112-
name: result.name,
113-
error: result.error,
114-
ops: result.hz,
115-
deviation: result.stats.rme,
116-
numRuns: result.stats.sample.length,
117-
}));
130+
function collectSamples(fn) {
131+
clock(initCount, fn); // initial warm up
132+
133+
// Cycles a benchmark until a run `count` can be established.
134+
// Resolve time span required to achieve a percent uncertainty of at most 1%.
135+
// For more information see http://spiff.rit.edu/classes/phys273/uncert/uncert.html.
136+
let count = initCount;
137+
let clocked = 0;
138+
while ((clocked = clock(count, fn)) < minTime) {
139+
// Calculate how many more iterations it will take to achieve the `minTime`.
140+
count += Math.ceil(((minTime - clocked) * count) / clocked);
141+
}
118142

119-
const nameMaxLen = maxBy(benches, ({ name }) => name.length);
120-
const opsTop = maxBy(benches, ({ ops }) => ops);
121-
const opsMaxLen = maxBy(benches, ({ ops }) => beautifyNumber(ops).length);
143+
let elapsed = 0;
144+
const samples = [];
122145

123-
for (const bench of benches) {
124-
if (bench.error) {
125-
console.log(' ' + bench.name + ': ' + red(String(bench.error)));
126-
continue;
127-
}
128-
printBench(bench);
146+
// If time permits, increase sample size to reduce the margin of error.
147+
while (samples.length < minSamples || elapsed < maxTime) {
148+
clocked = clock(count, fn);
149+
assert(clocked > 0);
150+
151+
elapsed += clocked;
152+
// Compute the seconds per operation.
153+
samples.push(clocked / count);
154+
}
155+
156+
return samples;
157+
}
158+
159+
// Clocks the time taken to execute a test per cycle (secs).
160+
function clock(count, fn) {
161+
const start = process.hrtime.bigint();
162+
for (let i = 0; i < count; ++i) {
163+
fn();
164+
}
165+
return Number(process.hrtime.bigint() - start);
166+
}
167+
168+
// T-Distribution two-tailed critical values for 95% confidence.
169+
// See http://www.itl.nist.gov/div898/handbook/eda/section3/eda3672.htm.
170+
const tTable = /* prettier-ignore */ {
171+
'1': 12.706, '2': 4.303, '3': 3.182, '4': 2.776, '5': 2.571, '6': 2.447,
172+
'7': 2.365, '8': 2.306, '9': 2.262, '10': 2.228, '11': 2.201, '12': 2.179,
173+
'13': 2.16, '14': 2.145, '15': 2.131, '16': 2.12, '17': 2.11, '18': 2.101,
174+
'19': 2.093, '20': 2.086, '21': 2.08, '22': 2.074, '23': 2.069, '24': 2.064,
175+
'25': 2.06, '26': 2.056, '27': 2.052, '28': 2.048, '29': 2.045, '30': 2.042,
176+
infinity: 1.96,
177+
};
178+
179+
// Computes stats on benchmark results.
180+
function computeStats(samples) {
181+
assert(samples.length > 1);
182+
183+
// Compute the sample mean (estimate of the population mean).
184+
let mean = 0;
185+
for (const x of samples) {
186+
mean += x;
187+
}
188+
mean /= samples.length;
189+
190+
// Compute the sample variance (estimate of the population variance).
191+
let variance = 0;
192+
for (const x of samples) {
193+
variance += Math.pow(x - mean, 2);
194+
}
195+
variance /= samples.length - 1;
196+
197+
// Compute the sample standard deviation (estimate of the population standard deviation).
198+
const sd = Math.sqrt(variance);
199+
200+
// Compute the standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean).
201+
const sem = sd / Math.sqrt(samples.length);
202+
203+
// Compute the degrees of freedom.
204+
const df = samples.length - 1;
205+
206+
// Compute the critical value.
207+
const critical = tTable[df] || tTable.infinity;
208+
209+
// Compute the margin of error.
210+
const moe = sem * critical;
211+
212+
// The relative margin of error (expressed as a percentage of the mean).
213+
const rme = (moe / mean) * 100 || 0;
214+
215+
return {
216+
ops: NS_PER_SEC / mean,
217+
deviation: rme,
218+
};
219+
}
220+
221+
function beautifyBenchmark(results) {
222+
const nameMaxLen = maxBy(results, ({ name }) => name.length);
223+
const opsTop = maxBy(results, ({ ops }) => ops);
224+
const opsMaxLen = maxBy(results, ({ ops }) => beautifyNumber(ops).length);
225+
226+
for (const result of results) {
227+
printBench(result);
129228
}
130229

131230
function printBench(bench) {
132-
const { name, ops, deviation, numRuns } = bench;
231+
const { name, ops, deviation, samples } = bench;
133232
console.log(
134233
' ' +
135234
nameStr() +
@@ -139,7 +238,7 @@ function beautifyBenchmark(results) {
139238
grey('\xb1') +
140239
deviationStr() +
141240
cyan('%') +
142-
grey(' (' + numRuns + ' runs sampled)'),
241+
grey(' (' + samples.length + ' runs sampled)'),
143242
);
144243

145244
function nameStr() {
@@ -160,22 +259,6 @@ function beautifyBenchmark(results) {
160259
}
161260
}
162261

163-
function red(str) {
164-
return '\u001b[31m' + str + '\u001b[0m';
165-
}
166-
function green(str) {
167-
return '\u001b[32m' + str + '\u001b[0m';
168-
}
169-
function yellow(str) {
170-
return '\u001b[33m' + str + '\u001b[0m';
171-
}
172-
function cyan(str) {
173-
return '\u001b[36m' + str + '\u001b[0m';
174-
}
175-
function grey(str) {
176-
return '\u001b[90m' + str + '\u001b[0m';
177-
}
178-
179262
function beautifyNumber(num) {
180263
return Number(num.toFixed(num > 100 ? 0 : 2)).toLocaleString();
181264
}

resources/colors.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// @noflow
2+
3+
'use strict';
4+
5+
function red(str) {
6+
return '\u001b[31m' + str + '\u001b[0m';
7+
}
8+
9+
function green(str) {
10+
return '\u001b[32m' + str + '\u001b[0m';
11+
}
12+
13+
function yellow(str) {
14+
return '\u001b[33m' + str + '\u001b[0m';
15+
}
16+
17+
function cyan(str) {
18+
return '\u001b[36m' + str + '\u001b[0m';
19+
}
20+
21+
function grey(str) {
22+
return '\u001b[90m' + str + '\u001b[0m';
23+
}
24+
25+
module.exports = {
26+
red,
27+
green,
28+
yellow,
29+
cyan,
30+
grey,
31+
};

yarn.lock

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -751,14 +751,6 @@ balanced-match@^1.0.0:
751751
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
752752
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
753753

754-
benchmark@2.1.4:
755-
version "2.1.4"
756-
resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629"
757-
integrity sha1-CfPeMckWQl1JjMLuVloOvzwqVik=
758-
dependencies:
759-
lodash "^4.17.4"
760-
platform "^1.3.3"
761-
762754
brace-expansion@^1.1.7:
763755
version "1.1.11"
764756
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -1699,7 +1691,7 @@ lodash.flattendeep@^4.4.0:
16991691
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
17001692
integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=
17011693

1702-
lodash@^4.17.11, lodash@^4.17.4:
1694+
lodash@^4.17.11:
17031695
version "4.17.11"
17041696
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
17051697
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
@@ -2129,11 +2121,6 @@ pkg-dir@^3.0.0:
21292121
dependencies:
21302122
find-up "^3.0.0"
21312123

2132-
platform@^1.3.3:
2133-
version "1.3.5"
2134-
resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444"
2135-
integrity sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==
2136-
21372124
prelude-ls@~1.1.2:
21382125
version "1.1.2"
21392126
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"

0 commit comments

Comments
 (0)