Skip to content

Commit df6708b

Browse files
committed
benchmark: add --csv option to compare.js with --analyze
Add a `--csv {filename}` option to benchmark/compare.js to capture the CSV when the `--analyze` option is used Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode
1 parent dab3a73 commit df6708b

3 files changed

Lines changed: 87 additions & 11 deletions

File tree

‎benchmark/compare.js‎

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const { spawn, fork } = require('node:child_process');
4+
const { closeSync, openSync, writeSync } = require('node:fs');
45
const { inspect } = require('util');
56
const path = require('path');
67
const CLI = require('./_cli.js');
@@ -27,7 +28,9 @@ const cli = new CLI(`usage: ./node compare.js [options] [--] <category> ...
2728
--no-progress don't show benchmark progress indicator
2829
--analyze perform statistical analysis after benchmarks
2930
complete (Welch's t-test, effect size) instead
30-
of printing csv output
31+
of printing csv output to stdout
32+
--csv filename write csv output to filename (can be combined
33+
with --analyze)
3134
--scale 1000 rate-to-integer multiplier for histogram
3235
precision when using --analyze (default: 1000)
3336
--max-regression N exit with code 1 if any statistically
@@ -60,6 +63,19 @@ if (benchmarks.length === 0) {
6063
return;
6164
}
6265

66+
const csvFd = cli.optional.csv === undefined ?
67+
null :
68+
openSync(cli.optional.csv, 'w');
69+
const outputCsv = !analyze || csvFd !== null;
70+
71+
function writeCsv(line) {
72+
if (csvFd === null) {
73+
console.log(line);
74+
} else {
75+
writeSync(csvFd, `${line}\n`);
76+
}
77+
}
78+
6379
// When --analyze is set, collect results for statistical analysis.
6480
const results = analyze ? new Map() : null;
6581

@@ -78,17 +94,19 @@ for (const filename of benchmarks) {
7894
}
7995
// queue.length = binary.length * runs * benchmarks.length
8096

81-
// Print csv header (unless analyzing inline).
82-
if (!analyze) {
83-
console.log('"binary","filename","configuration","rate","time"');
97+
// Print csv header unless only analyzing inline.
98+
if (outputCsv) {
99+
writeCsv('"binary","filename","configuration","rate","time"');
84100
}
85101

86102
const kStartOfQueue = 0;
87103

88104
const showProgress = !cli.optional['no-progress'];
89105
let progress;
90106
if (showProgress) {
91-
progress = new BenchmarkProgress(queue, benchmarks, { analyze });
107+
progress = new BenchmarkProgress(queue, benchmarks, {
108+
analyze: analyze || csvFd !== null,
109+
});
92110
progress.startQueue(kStartOfQueue);
93111
}
94112

@@ -126,11 +144,13 @@ if (showProgress) {
126144
results.set(name, { old: [], new: [] });
127145
}
128146
results.get(name)[job.binary].push(data.rate);
129-
} else {
147+
}
148+
149+
if (outputCsv) {
130150
// Escape quotes (") for correct csv formatting
131-
conf = conf.replace(/"/g, '""');
132-
console.log(`"${job.binary}","${job.filename}","${conf}",` +
133-
`${data.rate},${data.time}`);
151+
const csvConf = conf.replace(/"/g, '""');
152+
writeCsv(`"${job.binary}","${job.filename}","${csvConf}",` +
153+
`${data.rate},${data.time}`);
134154
}
135155
if (showProgress) {
136156
// One item in the subqueue has been completed.
@@ -153,8 +173,9 @@ if (showProgress) {
153173
// If there are more benchmarks execute the next
154174
if (i + 1 < queue.length) {
155175
recursive(i + 1);
156-
} else if (analyze) {
157-
printAnalysis(results, scale, maxRegression);
176+
} else {
177+
if (csvFd !== null) closeSync(csvFd);
178+
if (analyze) printAnalysis(results, scale, maxRegression);
158179
}
159180
});
160181
})(kStartOfQueue);

‎doc/contributing/writing-and-running-benchmarks.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,8 @@ module, you can use the `--filter` option:_
416416
--set variable=value set benchmark variable (can be repeated)
417417
--no-progress don't show benchmark progress indicator
418418
--analyze perform statistical analysis inline (no R needed)
419+
--csv filename write csv output to filename (can be combined
420+
with --analyze)
419421
--scale 1000 rate multiplier for --analyze precision
420422
--max-regression N exit with code 1 if any significant regression
421423
exceeds N% (implies --analyze)
@@ -429,6 +431,14 @@ The simplest way to get statistical results is to pass `--analyze`:
429431
node benchmark/compare.js --old ./node-main --new ./node-pr-5134 --analyze string_decoder
430432
```
431433

434+
Use `--csv` to retain the raw benchmark results while also printing the
435+
analysis:
436+
437+
```bash
438+
node benchmark/compare.js --old ./node-main --new ./node-pr-5134 \
439+
--analyze --csv compare-pr-5134.csv string_decoder
440+
```
441+
432442
This runs the benchmarks and prints the analysis directly:
433443

434444
```console
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
'use strict';
2+
3+
require('../common');
4+
5+
const assert = require('node:assert');
6+
const { spawnSync } = require('node:child_process');
7+
const { readFileSync } = require('node:fs');
8+
const path = require('node:path');
9+
const tmpdir = require('../common/tmpdir');
10+
11+
const compare = path.resolve(__dirname, '../../benchmark/compare.js');
12+
13+
tmpdir.refresh();
14+
15+
const csv = tmpdir.resolve('compare.csv');
16+
const result = spawnSync(process.execPath, [
17+
compare,
18+
'--old', process.execPath,
19+
'--new', process.execPath,
20+
'--runs', '1',
21+
'--filter', 'buffer-compare-offset.js',
22+
'--set', 'method=offset',
23+
'--set', 'size=16',
24+
'--set', 'n=1',
25+
'--no-progress',
26+
'--analyze',
27+
'--csv', csv,
28+
'buffers',
29+
], {
30+
encoding: 'utf8',
31+
timeout: 30_000,
32+
});
33+
34+
assert.strictEqual(result.status, 0, result.stderr);
35+
assert.strictEqual(result.stderr, '');
36+
assert.match(result.stdout, /confidence\s+improvement\s+accuracy/);
37+
assert.doesNotMatch(result.stdout, /"binary","filename"/);
38+
39+
const lines = readFileSync(csv, 'utf8').trim().split('\n');
40+
const filename = path.join('buffers', 'buffer-compare-offset.js');
41+
assert.strictEqual(lines[0],
42+
'"binary","filename","configuration","rate","time"');
43+
assert.strictEqual(lines.length, 3);
44+
assert(lines[1].startsWith(`"old","${filename}",`));
45+
assert(lines[2].startsWith(`"new","${filename}",`));

0 commit comments

Comments
 (0)