Skip to content

Commit a1ba417

Browse files
committed
src: use worker threads for each benchmark
1 parent f0f6d62 commit a1ba417

File tree

5 files changed

+175
-132
lines changed

5 files changed

+175
-132
lines changed

index.js

Lines changed: 7 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,18 @@
11
const fs = require('node:fs/promises');
2-
const { setTimeout: delay } = require('node:timers/promises');
32
const path = require('node:path');
4-
const { spawn } = require('node:child_process');
5-
const assert = require('node:assert');
3+
const Piscina = require('piscina');
64

7-
const autocannon = require('autocannon');
8-
const Benchmark = require('benchmark');
9-
10-
const runner = {
11-
autocannon: (opts) => {
12-
return autocannon({
13-
url: `http://localhost:${opts.http.serverPort}`,
14-
connections: 100,
15-
pipelining: 1,
16-
duration: 10 * opts.http.routes.length,
17-
requests: opts.http.routes,
18-
})
19-
},
20-
benchmarkjs: (opts) => {
21-
const suite = new Benchmark.Suite;
22-
23-
for (const operation of opts.operations) {
24-
suite.add(operation.name, operation.fn);
25-
}
26-
27-
return new Promise((resolve) => {
28-
const results = [];
29-
suite.on('cycle', function(event) {
30-
results.push({
31-
name: event.target.name,
32-
opsSec: event.target.hz,
33-
samples: event.target.cycles,
34-
});
35-
}).on('complete', function () {
36-
resolve(results);
37-
})
38-
.run({ 'async': false });
39-
})
40-
},
41-
}
42-
43-
const parser = {
44-
autocannon: (settings, result) => {
45-
return {
46-
name: settings.name,
47-
method: 'autocannon',
48-
http: {
49-
totalReq: asNumber(result.requests),
50-
duration: result.duration,
51-
errors: result.errors,
52-
}
53-
};
54-
},
55-
benchmarkjs: (settings, result) => {
56-
return {
57-
name: settings.name,
58-
method: 'benchmarkjs',
59-
operations: result,
60-
}
61-
}
62-
}
63-
64-
const ALLOWED_BENCHMARKER = ['autocannon', 'benchmarkjs'];
65-
66-
function asNumber (stat) {
67-
const result = Object.create(null)
68-
for (const k of Object.keys(stat)) {
69-
result[k] = stat[k].toLocaleString(undefined, {
70-
// to show all digits
71-
maximumFractionDigits: 20
72-
})
73-
}
74-
return result
75-
}
76-
77-
function spawnServer(settings) {
78-
const server = spawn(
79-
process.execPath,
80-
[settings.http.server],
81-
{ stdio: 'inherit' },
82-
);
83-
return server;
84-
}
85-
86-
async function runBenchmark(settings) {
87-
assert.ok(ALLOWED_BENCHMARKER.includes(settings.benchmarker), 'Invalid settings.benchmarker');
88-
89-
let server = undefined;
90-
if (settings.type === 'http') {
91-
assert.ok(settings.http.server, 'HTTP Benchmark must have a server to be spawned');
92-
server = spawnServer(settings);
93-
// TODO: replace this workaround to use IPC to know when server is up
94-
await delay(1000);
95-
}
96-
97-
const benchRunner = runner[settings.benchmarker];
98-
const benchParser = parser[settings.benchmarker];
99-
const results = await benchRunner(settings);
100-
if (server) {
101-
server.kill('SIGINT');
102-
}
103-
return benchParser(settings, results);
104-
}
5+
const piscina = new Piscina({
6+
filename: path.resolve(__dirname, 'worker.js')
7+
});
1058

1069
async function main() {
10710
const files = await fs.readdir(path.join(__dirname, './src'));
10811
for (const file of files) {
10912
if (file.match(/.*-benchmark\.js$/)) {
110-
const bench = require(path.join(__dirname, './src/', file));
111-
// TODO(rafaelgss): possibly should be more accurate to run each benchmark in
112-
// a separate worker
113-
const result = await runBenchmark(bench)
114-
console.log(bench.name, 'results', JSON.stringify(result, null, 2));
13+
const benchFile = path.join(__dirname, './src/', file);
14+
const result = await piscina.run(benchFile);
15+
console.log('results', JSON.stringify(result, null, 2));
11516
}
11617
console.log('Done...', file)
11718
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"dependencies": {
33
"fastify": "4.26.1",
44
"lodash": "4.17.21",
5+
"piscina": "4.4.0",
56
"prettier": "3.2.5"
67
},
78
"devDependencies": {

pnpm-lock.yaml

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/prettier-benchmark.js

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,32 @@ module.exports = {
2020
return v;
2121
},
2222
},
23-
// {
24-
// name: 'format (singleQuote=true semi=true tabs=true)',
25-
// fn: () => {
26-
// let v = undefined;
27-
// for (const p of payloads) {
28-
// v = prettier.format(
29-
// p,
30-
// { singleQuote: true, semi: true, tabs: true, parser: 'babel' },
31-
// );
32-
// }
33-
// return v;
34-
// },
35-
// },
36-
// {
37-
// name: 'format (singleQuote=false semi=false tabs=false)',
38-
// fn: () => {
39-
// let v = undefined;
40-
// for (const p of payloads) {
41-
// v = prettier.format(
42-
// p,
43-
// { singleQuote: false, semi: false, tabs: false, parser: 'babel' }
44-
// );
45-
// }
46-
// return v;
47-
// },
48-
// },
23+
{
24+
name: 'format (singleQuote=true semi=true tabs=true)',
25+
fn: () => {
26+
let v = undefined;
27+
for (const p of payloads) {
28+
v = prettier.format(
29+
p,
30+
{ singleQuote: true, semi: true, tabs: true, parser: 'babel' },
31+
);
32+
}
33+
return v;
34+
},
35+
},
36+
{
37+
name: 'format (singleQuote=false semi=false tabs=false)',
38+
fn: () => {
39+
let v = undefined;
40+
for (const p of payloads) {
41+
v = prettier.format(
42+
p,
43+
{ singleQuote: false, semi: false, tabs: false, parser: 'babel' }
44+
);
45+
}
46+
return v;
47+
},
48+
},
4949
],
5050
benchmarker: 'benchmarkjs',
5151
};

worker.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
const { spawn } = require('node:child_process');
2+
const assert = require('node:assert');
3+
4+
const autocannon = require('autocannon');
5+
const Benchmark = require('benchmark');
6+
7+
const { setTimeout: delay } = require('node:timers/promises');
8+
9+
const runner = {
10+
autocannon: (opts) => {
11+
return autocannon({
12+
url: `http://localhost:${opts.http.serverPort}`,
13+
connections: 100,
14+
pipelining: 1,
15+
duration: 10 * opts.http.routes.length,
16+
requests: opts.http.routes,
17+
})
18+
},
19+
benchmarkjs: (opts) => {
20+
const suite = new Benchmark.Suite;
21+
22+
for (const operation of opts.operations) {
23+
suite.add(operation.name, operation.fn);
24+
}
25+
26+
return new Promise((resolve) => {
27+
const results = [];
28+
suite.on('cycle', function(event) {
29+
results.push({
30+
name: event.target.name,
31+
opsSec: event.target.hz,
32+
samples: event.target.cycles,
33+
});
34+
}).on('complete', function () {
35+
resolve(results);
36+
})
37+
.run({ 'async': false });
38+
})
39+
},
40+
}
41+
42+
const parser = {
43+
autocannon: (settings, result) => {
44+
return {
45+
name: settings.name,
46+
method: 'autocannon',
47+
http: {
48+
totalReq: asNumber(result.requests),
49+
duration: result.duration,
50+
errors: result.errors,
51+
}
52+
};
53+
},
54+
benchmarkjs: (settings, result) => {
55+
return {
56+
name: settings.name,
57+
method: 'benchmarkjs',
58+
operations: result,
59+
}
60+
}
61+
}
62+
63+
const ALLOWED_BENCHMARKER = ['autocannon', 'benchmarkjs'];
64+
65+
function asNumber (stat) {
66+
const result = Object.create(null)
67+
for (const k of Object.keys(stat)) {
68+
result[k] = stat[k].toLocaleString(undefined, {
69+
// to show all digits
70+
maximumFractionDigits: 20
71+
})
72+
}
73+
return result
74+
}
75+
76+
function spawnServer(settings) {
77+
const server = spawn(
78+
process.execPath,
79+
[settings.http.server],
80+
{ stdio: 'inherit' },
81+
);
82+
return server;
83+
}
84+
85+
async function runBenchmark(settings) {
86+
assert.ok(ALLOWED_BENCHMARKER.includes(settings.benchmarker), 'Invalid settings.benchmarker');
87+
88+
let server = undefined;
89+
if (settings.type === 'http') {
90+
assert.ok(settings.http.server, 'HTTP Benchmark must have a server to be spawned');
91+
server = spawnServer(settings);
92+
// TODO: replace this workaround to use IPC to know when server is up
93+
await delay(1000);
94+
}
95+
96+
const benchRunner = runner[settings.benchmarker];
97+
const benchParser = parser[settings.benchmarker];
98+
const results = await benchRunner(settings);
99+
if (server) {
100+
server.kill('SIGINT');
101+
}
102+
return benchParser(settings, results);
103+
}
104+
105+
async function main (benchFile) {
106+
const bench = require(benchFile);
107+
const result = await runBenchmark(bench);
108+
return result;
109+
}
110+
111+
module.exports = main;

0 commit comments

Comments
 (0)