Skip to content

Commit 04580f4

Browse files
committed
Benchmark: Inline code from benchmark.js
1 parent 758d08a commit 04580f4

File tree

3 files changed

+128
-25
lines changed

3 files changed

+128
-25
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: 127 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
const os = require('os');
66
const fs = require('fs');
77
const path = require('path');
8-
const { Benchmark } = require('benchmark');
98

109
const {
1110
exec,
@@ -16,6 +15,7 @@ const {
1615
readdirRecursive,
1716
} = require('./utils');
1817

18+
const NS_PER_SEC = 1e9;
1919
const LOCAL = 'local';
2020

2121
function LOCAL_DIR(...paths) {
@@ -93,12 +93,20 @@ function runBenchmark(benchmark, environments) {
9393
const benches = environments.map(environment => {
9494
const module = require(path.join(environment.distPath, benchmark));
9595
benchmarkName = module.name;
96-
return new Benchmark(environment.revision, module.measure);
96+
return {
97+
name: environment.revision,
98+
fn: module.measure,
99+
};
97100
});
98101

99102
console.log('⏱️ ' + benchmarkName);
100103
for (let i = 0; i < benches.length; ++i) {
101-
benches[i].run({ async: false });
104+
const bench = benches[i];
105+
try {
106+
bench.sample = run(bench.fn);
107+
} catch (e) {
108+
bench.error = e;
109+
}
102110
process.stdout.write(' ' + cyan(i + 1) + ' tests completed.\u000D');
103111
}
104112
console.log('\n');
@@ -107,14 +115,123 @@ function runBenchmark(benchmark, environments) {
107115
console.log('');
108116
}
109117

118+
// Clocks the time taken to execute a test per cycle (secs).
119+
function clock(count, fn) {
120+
const start = process.hrtime.bigint();
121+
for (let i = count; count !== 0; --count) {
122+
fn();
123+
}
124+
return Number(process.hrtime.bigint() - start);
125+
}
126+
127+
// Cycles a benchmark until a run `count` can be established.
128+
function cycle(initCount, fn) {
129+
// Resolve time span required to achieve a percent uncertainty of at most 1%.
130+
// For more information see http://spiff.rit.edu/classes/phys273/uncert/uncert.html.
131+
const minTime = 0.05 * NS_PER_SEC;
132+
let count = initCount;
133+
let clocked = 0;
134+
135+
while (true) {
136+
clocked = clock(count, fn);
137+
138+
// Do we need to do another cycle?
139+
if (clocked >= minTime) {
140+
break;
141+
}
142+
143+
// Calculate how many more iterations it will take to achieve the `minTime`.
144+
count += Math.ceil((minTime - clocked) / clocked * count);
145+
}
146+
147+
return count;
148+
}
149+
150+
// Computes stats on benchmark results.
151+
function run(fn) {
152+
const bench = this;
153+
const sample = [];
154+
155+
// The maximum time a benchmark is allowed to run before finishing.
156+
const maxTime = 5 * NS_PER_SEC;
157+
// The minimum sample size required to perform statistical analysis.
158+
const minSamples = 15;
159+
// The default number of times to execute a test on a benchmark's first cycle.
160+
const initCount = 10;
161+
162+
const count = cycle(initCount, fn);
163+
clock(count, fn); // initial warm up
164+
// If time permits, increase sample size to reduce the margin of error.
165+
let elapsed = 0;
166+
while (sample.length < minSamples || elapsed < maxTime) {
167+
const clocked = clock(count, fn);
168+
elapsed += clocked;
169+
// Compute the seconds per operation.
170+
sample.push(clocked / count);
171+
}
172+
173+
return sample;
174+
}
175+
176+
/*
177+
* T-Distribution two-tailed critical values for 95% confidence.
178+
* For more info see http://www.itl.nist.gov/div898/handbook/eda/section3/eda3672.htm.
179+
*/
180+
var tTable = {
181+
'1': 12.706, '2': 4.303, '3': 3.182, '4': 2.776, '5': 2.571, '6': 2.447,
182+
'7': 2.365, '8': 2.306, '9': 2.262, '10': 2.228, '11': 2.201, '12': 2.179,
183+
'13': 2.16, '14': 2.145, '15': 2.131, '16': 2.12, '17': 2.11, '18': 2.101,
184+
'19': 2.093, '20': 2.086, '21': 2.08, '22': 2.074, '23': 2.069, '24': 2.064,
185+
'25': 2.06, '26': 2.056, '27': 2.052, '28': 2.048, '29': 2.045, '30': 2.042,
186+
'infinity': 1.96
187+
};
188+
110189
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-
}));
190+
const benches = results.map(result => {
191+
const sample = result.sample || [];
192+
193+
// Compute the sample mean (estimate of the population mean).
194+
let mean = 0;
195+
for (const x of sample) {
196+
mean += x;
197+
}
198+
mean /= sample.length;
199+
mean = mean || 0;
200+
201+
// Compute the sample variance (estimate of the population variance).
202+
let variance = 0;
203+
for (const x of sample) {
204+
variance += Math.pow(x - mean, 2);
205+
}
206+
variance /= (sample.length - 1);
207+
variance = variance || 0;
208+
209+
// Compute the sample standard deviation (estimate of the population standard deviation).
210+
const sd = Math.sqrt(variance);
211+
212+
// Compute the standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean).
213+
const sem = sd / Math.sqrt(sample.length);
214+
215+
// Compute the degrees of freedom.
216+
const df = sample.length - 1;
217+
218+
// Compute the critical value.
219+
const critical = tTable[Math.round(df) || 1] || tTable.infinity;
220+
221+
// Compute the margin of error.
222+
const moe = sem * critical;
223+
224+
// The relative margin of error (expressed as a percentage of the mean).
225+
const rme = (moe / mean) * 100 || 0;
226+
227+
return {
228+
name: result.name,
229+
error: result.error,
230+
ops: NS_PER_SEC / mean,
231+
deviation: rme,
232+
numRuns: sample.length,
233+
};
234+
});
118235

119236
const nameMaxLen = maxBy(benches, ({ name }) => name.length);
120237
const opsTop = maxBy(benches, ({ ops }) => ops);

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)