Skip to content

Commit 9356d0d

Browse files
RafaelGSSH4ad
andcommitted
lib: add experimental benchmark module
Co-authored-by: Vinícius Lourenço Claro Cardoso <[email protected]>
1 parent 416333f commit 9356d0d

14 files changed

+1272
-0
lines changed

doc/api/benchmark.md

+293
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
# Benchmark
2+
3+
<!--introduced_in=REPLACEME-->
4+
5+
> Stability: 1.1 - Active Development
6+
7+
<!-- source_link=lib/benchmark.js -->
8+
9+
The `node:benchmark` module gives the ability to measure
10+
performance of JavaScript code. To access it:
11+
12+
```mjs
13+
import benchmark from 'node:benchmark';
14+
```
15+
16+
```cjs
17+
const benchmark = require('node:benchmark');
18+
```
19+
20+
This module is only available under the `node:` scheme. The following will not
21+
work:
22+
23+
```mjs
24+
import benchmark from 'benchmark';
25+
```
26+
27+
```cjs
28+
const benchmark = require('benchmark');
29+
```
30+
31+
The following example illustrates how benchmarks are written using the
32+
`benchmark` module.
33+
34+
```mjs
35+
import { Suite } from 'node:benchmark';
36+
37+
const suite = new Suite();
38+
39+
suite.add('Using delete to remove property from object', function() {
40+
const data = { x: 1, y: 2, z: 3 };
41+
delete data.y;
42+
43+
data.x;
44+
data.y;
45+
data.z;
46+
});
47+
48+
suite.run();
49+
```
50+
51+
```cjs
52+
const { Suite } = require('node:benchmark');
53+
54+
const suite = new Suite();
55+
56+
suite.add('Using delete to remove property from object', function() {
57+
const data = { x: 1, y: 2, z: 3 };
58+
delete data.y;
59+
60+
data.x;
61+
data.y;
62+
data.z;
63+
});
64+
65+
suite.run();
66+
```
67+
68+
```console
69+
$ node my-benchmark.js
70+
(node:14165) ExperimentalWarning: The benchmark module is an experimental feature and might change at any time
71+
(Use `node --trace-warnings ...` to show where the warning was created)
72+
Using delete property x 5,853,505 ops/sec ± 0.01% (10 runs sampled) min..max=(169ns ... 171ns) p75=170ns p99=171ns
73+
```
74+
75+
## Class: `Suite`
76+
77+
> Stability: 1.1 Active Development
78+
79+
<!-- YAML
80+
added: REPLACEME
81+
-->
82+
83+
An `Suite` is responsible for managing and executing
84+
benchmark functions. It provides two methods: `add()` and `run()`.
85+
86+
### `new Suite([options])`
87+
88+
<!-- YAML
89+
added: REPLACEME
90+
-->
91+
92+
* `options` {Object} Configuration options for the suite. The following
93+
properties are supported:
94+
* `reporter` {Function} Callback function with results to be called after
95+
benchmark is concluded. The callback function should receive two arguments:
96+
`suite` - A {Suite} object and
97+
`result` - A object containing three properties:
98+
`opsSec` {string}, `iterations {Number}`, `histogram` {Histogram} instance.
99+
100+
If no `reporter` is provided, the results will printed to the console.
101+
102+
```mjs
103+
import { Suite } from 'node:benchmark';
104+
const suite = new Suite();
105+
```
106+
107+
```cjs
108+
const { Suite } = require('node:benchmark');
109+
const suite = new Suite();
110+
```
111+
112+
### `suite.add(name[, options], fn)`
113+
114+
<!-- YAML
115+
added: REPLACEME
116+
-->
117+
118+
* `name` {string} The name of the benchmark, which is displayed when reporting
119+
benchmark results.
120+
* `options` {Object} Configuration options for the benchmark. The following
121+
properties are supported:
122+
* `minTime` {number} The minimum time a benchmark can run.
123+
**Default:** `0.05` seconds.
124+
* `maxTime` {number} The maximum time a benchmark can run.
125+
**Default:** `0.5` seconds.
126+
* `fn` {Function|AsyncFunction}
127+
* Returns: {Suite}
128+
129+
This method stores the benchmark of a given function (`fn`).
130+
The `fn` parameter can be either an asynchronous (`async function () {}`) or
131+
a synchronous (`function () {}`) function.
132+
133+
```console
134+
$ node my-benchmark.js
135+
(node:14165) ExperimentalWarning: The benchmark module is an experimental feature and might change at any time
136+
(Use `node --trace-warnings ...` to show where the warning was created)
137+
Using delete property x 5,853,505 ops/sec ± 0.01% (10 runs sampled) min..max=(169ns ... 171ns) p75=170ns p99=171ns
138+
```
139+
140+
### `suite.run()`
141+
142+
* Returns: `{Promise<Array<Object>>}`
143+
* `opsSec` {number} The amount of operations per second
144+
* `iterations` {number} The amount executions of `fn`
145+
* `histogram` {Histogram} Histogram object used to record benchmark iterations
146+
147+
<!-- YAML
148+
added: REPLACEME
149+
-->
150+
151+
The purpose of the run method is to run all the benchmarks that have been
152+
added to the suite using the [`suite.add()`][] function.
153+
By calling the run method, you can easily trigger the execution of all
154+
the stored benchmarks and obtain the corresponding results.
155+
156+
### Using custom reporter
157+
158+
You can customize the data reporting by passing an function to the `reporter` argument while creating your `Suite`:
159+
160+
```mjs
161+
import { Suite } from 'node:benchmark';
162+
163+
function reporter(bench, result) {
164+
console.log(`Benchmark: ${bench.name} - ${result.opsSec} ops/sec`);
165+
}
166+
167+
const suite = new Suite({ reporter });
168+
169+
suite.add('Using delete to remove property from object', () => {
170+
const data = { x: 1, y: 2, z: 3 };
171+
delete data.y;
172+
173+
data.x;
174+
data.y;
175+
data.z;
176+
});
177+
178+
suite.run();
179+
```
180+
181+
```cjs
182+
const { Suite } = require('node:benchmark');
183+
184+
function reporter(bench, result) {
185+
console.log(`Benchmark: ${bench.name} - ${result.opsSec} ops/sec`);
186+
}
187+
188+
const suite = new Suite({ reporter });
189+
190+
suite.add('Using delete to remove property from object', () => {
191+
const data = { x: 1, y: 2, z: 3 };
192+
delete data.y;
193+
194+
data.x;
195+
data.y;
196+
data.z;
197+
});
198+
199+
suite.run();
200+
```
201+
202+
```console
203+
$ node my-benchmark.js
204+
Benchmark: Using delete to remove property from object - 6032212 ops/sec
205+
```
206+
207+
### Setup and Teardown
208+
209+
The benchmark function has a special handling when you pass an argument,
210+
for example:
211+
212+
```cjs
213+
const { Suite } = require('node:benchmark');
214+
const { readFileSync, writeFileSync, rmSync } = require('node:fs');
215+
216+
const suite = new Suite();
217+
218+
suite.add('readFileSync', (timer) => {
219+
const randomFile = Date.now();
220+
const filePath = `./${randomFile}.txt`;
221+
writeFileSync(filePath, Math.random().toString());
222+
223+
timer.start();
224+
readFileSync(filePath, 'utf8');
225+
timer.end();
226+
227+
rmSync(filePath);
228+
}).run();
229+
```
230+
231+
In this way, you can control when the `timer` will start
232+
and also when the `timer` will stop.
233+
234+
In the timer, we also give you a property `count`
235+
that will tell you how much iterations
236+
you should run your function to achieve the `benchmark.minTime`,
237+
see the following example:
238+
239+
```mjs
240+
import { Suite } from 'node:benchmark';
241+
import { readFileSync, writeFileSync, rmSync } from 'node:fs';
242+
243+
const suite = new Suite();
244+
245+
suite.add('readFileSync', (timer) => {
246+
const randomFile = Date.now();
247+
const filePath = `./${randomFile}.txt`;
248+
writeFileSync(filePath, Math.random().toString());
249+
250+
timer.start();
251+
for (let i = 0; i < timer.count; i++)
252+
readFileSync(filePath, 'utf8');
253+
// You must send to the `.end` function the amount of
254+
// times you executed the function, by default,
255+
// the end will be called with value 1.
256+
timer.end(timer.count);
257+
258+
rmSync(filePath);
259+
});
260+
261+
suite.run();
262+
```
263+
264+
```cjs
265+
const { Suite } = require('node:benchmark');
266+
const { readFileSync, writeFileSync, rmSync } = require('node:fs');
267+
268+
const suite = new Suite();
269+
270+
suite.add('readFileSync', (timer) => {
271+
const randomFile = Date.now();
272+
const filePath = `./${randomFile}.txt`;
273+
writeFileSync(filePath, Math.random().toString());
274+
275+
timer.start();
276+
for (let i = 0; i < timer.count; i++)
277+
readFileSync(filePath, 'utf8');
278+
// You must send to the `.end` function the amount of
279+
// times you executed the function, by default,
280+
// the end will be called with value 1.
281+
timer.end(timer.count);
282+
283+
rmSync(filePath);
284+
});
285+
286+
suite.run();
287+
```
288+
289+
Once your function has at least one argument,
290+
you must call `.start` and `.end`, if you didn't,
291+
it will throw the error [ERR\_BENCHMARK\_MISSING\_OPERATION](./errors.md#err_benchmark_missing_operation).
292+
293+
[`suite.add()`]: #suiteaddname-options-fn

doc/api/errors.md

+7
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,13 @@ An attempt was made to register something that is not a function as an
705705
The type of an asynchronous resource was invalid. Users are also able
706706
to define their own types if using the public embedder API.
707707

708+
<a id="ERR_BENCHMARK_MISSING_OPERATION"></a>
709+
710+
### `ERR_BENCHMARK_MISSING_OPERATION`
711+
712+
The user forgot to call .start or .end during the execution of
713+
the benchmark.
714+
708715
<a id="ERR_BROTLI_COMPRESSION_FAILED"></a>
709716

710717
### `ERR_BROTLI_COMPRESSION_FAILED`

doc/api/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* [Assertion testing](assert.md)
1414
* [Asynchronous context tracking](async_context.md)
1515
* [Async hooks](async_hooks.md)
16+
* [Benchmark](benchmark.md)
1617
* [Buffer](buffer.md)
1718
* [C++ addons](addons.md)
1819
* [C/C++ addons with Node-API](n-api.md)

lib/benchmark.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
'use strict';
2+
const { ObjectAssign } = primordials;
3+
const { Suite } = require('internal/benchmark/runner');
4+
const { emitExperimentalWarning } = require('internal/util');
5+
6+
emitExperimentalWarning('The benchmark module');
7+
8+
ObjectAssign(module.exports, {
9+
Suite,
10+
});

0 commit comments

Comments
 (0)