Skip to content

Commit b113ef7

Browse files
benchmark: collect memory usage per operation (#2088)
1 parent 0cd1903 commit b113ef7

File tree

3 files changed

+41
-11
lines changed

3 files changed

+41
-11
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"testonly": "mocha --full-trace src/**/__tests__/**/*-test.js",
2929
"testonly:cover": "nyc npm run testonly",
3030
"lint": "eslint --cache --report-unused-disable-directives src resources",
31-
"benchmark": "node --predictable ./resources/benchmark.js",
31+
"benchmark": "node --noconcurrent_sweeping --expose-gc --predictable ./resources/benchmark.js",
3232
"prettier": "prettier --ignore-path .gitignore --write --list-different \"**/*.{js,md,json,yml}\"",
3333
"prettier:check": "prettier --ignore-path .gitignore --check \"**/*.{js,md,json,yml}\"",
3434
"check": "flow check",

resources/benchmark-fork.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ function clock(count, fn) {
1111
for (let i = 0; i < count; ++i) {
1212
fn();
1313
}
14-
return Number(process.hrtime.bigint() - start) / count;
14+
return Number(process.hrtime.bigint() - start);
1515
}
1616

1717
if (require.main === module) {
@@ -21,10 +21,14 @@ if (require.main === module) {
2121
const module = require(modulePath);
2222

2323
clock(7, module.measure); // warm up
24+
global.gc();
2425
process.nextTick(() => {
26+
const memBaseline = process.memoryUsage().heapUsed;
27+
const clocked = clock(module.count, module.measure);
2528
process.send({
2629
name: module.name,
27-
clocked: clock(module.count, module.measure),
30+
clocked: clocked / module.count,
31+
memUsed: (process.memoryUsage().heapUsed - memBaseline) / module.count,
2832
});
2933
});
3034
}
@@ -44,6 +48,9 @@ function sampleModule(modulePath) {
4448
}
4549
reject(error || new Error('Forked process closed without error'));
4650
});
51+
}).then(result => {
52+
global.gc();
53+
return result;
4754
});
4855
}
4956

resources/benchmark.js

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,10 @@ async function collectSamples(modulePath) {
102102
// If time permits, increase sample size to reduce the margin of error.
103103
const start = Date.now();
104104
while (samples.length < minSamples || (Date.now() - start) / 1e3 < maxTime) {
105-
const { clocked } = await sampleModule(modulePath);
105+
const { clocked, memUsed } = await sampleModule(modulePath);
106106
assert(clocked > 0);
107-
samples.push(clocked);
107+
assert(memUsed > 0);
108+
samples.push({ clocked, memUsed });
108109
}
109110
return samples;
110111
}
@@ -126,15 +127,18 @@ function computeStats(samples) {
126127

127128
// Compute the sample mean (estimate of the population mean).
128129
let mean = 0;
129-
for (const x of samples) {
130-
mean += x;
130+
let meanMemUsed = 0;
131+
for (const { clocked, memUsed } of samples) {
132+
mean += clocked;
133+
meanMemUsed += memUsed;
131134
}
132135
mean /= samples.length;
136+
meanMemUsed /= samples.length;
133137

134138
// Compute the sample variance (estimate of the population variance).
135139
let variance = 0;
136-
for (const x of samples) {
137-
variance += Math.pow(x - mean, 2);
140+
for (const { clocked } of samples) {
141+
variance += Math.pow(clocked - mean, 2);
138142
}
139143
variance /= samples.length - 1;
140144

@@ -157,22 +161,28 @@ function computeStats(samples) {
157161
const rme = (moe / mean) * 100 || 0;
158162

159163
return {
164+
memPerOp: Math.floor(meanMemUsed),
160165
ops: NS_PER_SEC / mean,
161166
deviation: rme,
167+
numSamples: samples.length,
162168
};
163169
}
164170

165171
function beautifyBenchmark(results) {
166172
const nameMaxLen = maxBy(results, ({ name }) => name.length);
167173
const opsTop = maxBy(results, ({ ops }) => ops);
168174
const opsMaxLen = maxBy(results, ({ ops }) => beautifyNumber(ops).length);
175+
const memPerOpMaxLen = maxBy(
176+
results,
177+
({ memPerOp }) => beautifyBytes(memPerOp).length,
178+
);
169179

170180
for (const result of results) {
171181
printBench(result);
172182
}
173183

174184
function printBench(bench) {
175-
const { name, ops, deviation, samples } = bench;
185+
const { name, memPerOp, ops, deviation, numSamples } = bench;
176186
console.log(
177187
' ' +
178188
nameStr() +
@@ -182,7 +192,10 @@ function beautifyBenchmark(results) {
182192
grey('\xb1') +
183193
deviationStr() +
184194
cyan('%') +
185-
grey(' (' + samples.length + ' runs sampled)'),
195+
grey(' x ') +
196+
memPerOpStr() +
197+
'/op' +
198+
grey(' (' + numSamples + ' runs sampled)'),
186199
);
187200

188201
function nameStr() {
@@ -200,9 +213,19 @@ function beautifyBenchmark(results) {
200213
const colorFn = deviation > 5 ? red : deviation > 2 ? yellow : green;
201214
return colorFn(deviation.toFixed(2));
202215
}
216+
217+
function memPerOpStr() {
218+
return beautifyBytes(memPerOp).padStart(memPerOpMaxLen);
219+
}
203220
}
204221
}
205222

223+
function beautifyBytes(bytes) {
224+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
225+
const i = Math.floor(Math.log2(bytes) / 10);
226+
return beautifyNumber(bytes / Math.pow(2, i * 10)) + ' ' + sizes[i];
227+
}
228+
206229
function beautifyNumber(num) {
207230
return Number(num.toFixed(num > 100 ? 0 : 2)).toLocaleString();
208231
}

0 commit comments

Comments
 (0)