bench-node
Advanced tools
Comparing version 0.0.4-beta.1 to 0.0.4-beta.2
@@ -1,2 +0,2 @@ | ||
const { debug } = require('node:util'); | ||
const { debug, types } = require('node:util'); | ||
const { validateNumber } = require('./validators'); | ||
@@ -35,18 +35,18 @@ | ||
if (timeInNs > 1e9) { | ||
return `${ (timeInNs / 1e9, 2).toFixed() }s`; | ||
return `${ (timeInNs / 1e9).toFixed(2) }s`; | ||
} | ||
if (timeInNs > 1e6) { | ||
return `${ (timeInNs / 1e6, 2).toFixed() }ms`; | ||
return `${ (timeInNs / 1e6).toFixed(2) }ms`; | ||
} | ||
if (timeInNs > 1e3) { | ||
return `${ (timeInNs / 1e3, 2).toFixed() }us`; | ||
return `${ (timeInNs / 1e3).toFixed(2) }us`; | ||
} | ||
if (timeInNs > 1e2) { | ||
return `${ (timeInNs, 0).toFixed() }ns`; | ||
return `${ (timeInNs).toFixed(2) }ns`; | ||
} | ||
return `${ (timeInNs, 2).toFixed() }ns`; | ||
return `${ (timeInNs).toFixed(2) }ns`; | ||
} | ||
@@ -203,3 +203,3 @@ } | ||
function createRunner(bench, recommendedCount) { | ||
const isAsync = bench.fn.constructor === AsyncFunction; | ||
const isAsync = types.isAsyncFunction(bench.fn); | ||
const hasArg = bench.fn.length >= 1; | ||
@@ -209,3 +209,2 @@ | ||
process.emitWarning(`The benchmark "${ bench.name }" function should not have more than 1 argument.`); | ||
process.exit(1); | ||
} | ||
@@ -212,0 +211,0 @@ |
@@ -115,7 +115,10 @@ const { validateNumber } = require('./validators'); | ||
calculateStd() { | ||
const avgSquares = this.all.reduce( | ||
(acc, value) => Math.min(Number.MAX_SAFE_INTEGER, Math.pow(value, 2) + acc), 0, | ||
) * (1 / this.all.length); | ||
this.stddev = Math.sqrt(Math.max(0, this.all.length / (this.all.length - 1) * (avgSquares - (this.mean * this.mean)))); | ||
if (this.all.length < 2) { | ||
this.stddev = 0; | ||
return; | ||
} | ||
const variance = this.all.reduce((acc, value) => { | ||
return acc + Math.pow(value - this.mean, 2); | ||
}, 0) / (this.all.length - 1); | ||
this.stddev = Math.sqrt(variance); | ||
} | ||
@@ -129,3 +132,3 @@ | ||
calculateCv() { | ||
if (this.all.length < 2) { | ||
if (this.all.length < 2 || this.mean === 0) { | ||
this.cv = 0; | ||
@@ -132,0 +135,0 @@ return; |
@@ -16,3 +16,3 @@ const { clockBenchmark, debugBench, MIN_RESOLUTION, timer } = require('./clock'); | ||
const result = {} | ||
for (p of plugins) { | ||
for (const p of plugins) { | ||
result[p.toString()] = p.getResult?.() ?? 'enabled'; | ||
@@ -19,0 +19,0 @@ } |
{ | ||
"name": "bench-node", | ||
"version": "0.0.4-beta.1", | ||
"version": "0.0.4-beta.2", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
300
README.md
@@ -1,10 +0,9 @@ | ||
# bench-node | ||
# `bench-node` | ||
The `bench-node` module gives the ability to measure | ||
operations per second of Node.js code block | ||
The `bench-node` module allows you to measure operations per second of Node.js code blocks. | ||
## Install | ||
```console | ||
$ npm i bench-node | ||
```bash | ||
$ npm install bench-node | ||
``` | ||
@@ -17,5 +16,12 @@ | ||
const suite = new Suite(); | ||
const suite = new Suite({ | ||
reporter: (bench, result) => { | ||
console.log(`Benchmark: ${bench.name}`); | ||
console.log(`Operations per second: ${result.opsSec}`); | ||
console.log(`Iterations: ${result.iterations}`); | ||
console.log(`Histogram: ${result.histogram}`); | ||
} | ||
}); | ||
suite.add('Using delete to remove property from object', function() { | ||
suite.add('Using delete property', () => { | ||
const data = { x: 1, y: 2, z: 3 }; | ||
@@ -29,36 +35,26 @@ delete data.y; | ||
suite.run(); | ||
suite.run().then(results => { | ||
console.log('Benchmark complete.'); | ||
}).catch(err => { | ||
console.error('Error running benchmarks:', err); | ||
}); | ||
``` | ||
This module uses V8 deoptimization to guarantee the code block won't be eliminated producing | ||
a noop comparisson. See [writting JavasCript Microbenchmark mistakes][] section. | ||
This module uses V8 deoptimization to ensure that the code block is not optimized away, producing accurate benchmarks. See the [Writing JavaScript Microbenchmark Mistakes](#TODO) section for more details. | ||
```console | ||
```bash | ||
$ node --allow-natives-syntax my-benchmark.js | ||
Using delete property x 5,853,505 ops/sec ± 0.01% (10 runs sampled) min..max=(169ns ... 171ns) p75=170ns p99=171ns | ||
Using delete property x 3,326,913 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(0ns ... 0ns) p75=0ns p99=0ns | ||
``` | ||
See [examples folder](./examples/) for common usage. | ||
See the [examples folder](./examples/) for more common usage examples. | ||
## Table of Contents | ||
1. [class `Suite`](#class-suite) | ||
1. [Class `Suite`](#class-suite) | ||
1. [`suite.add()`](#suiteaddname-options-fn) | ||
2. [`suite.run()`](#suiterun) | ||
2. [Plugins](#plugins) | ||
1. [Structure](#structure) | ||
2. [Plugin Methods](#plugin-methods) | ||
* [`isSupported()` (required)](#issupported-required) | ||
* [`beforeClockTemplate(varNames)`](#beforeclocktemplatevarnames) | ||
* [`afterClockTemplate(varNames)`](#afterclocktemplatevarnames) | ||
* [`onCompleteBenchmark(result)`](#oncompletebenchmarkresult) | ||
* [`toString()` (required)](#tostring-required) | ||
3. [Example Plugins](#example-plugins) | ||
* [V8OptimizeOnNextCallPlugin](#class-v8optimizeonnextcallplugin) | ||
3. [Official Plugins](#official-plugins) | ||
* [Class: `V8OptimizeOnNextCallPlugin`](#class-v8optimizeonnextcallplugin-1) | ||
* [Class: `V8NeverOptimizePlugin`](#class-v8neveroptimizeplugin) | ||
* [Class: `V8GetOptimizationStatus`](#class-v8getoptimizationstatus) | ||
4. [Using custom reporter](#using-custom-reporter) | ||
5. [Setup and Teardown](#setup-and-teardown) | ||
3. [Using Custom Reporter](#using-custom-reporter) | ||
4. [Setup and Teardown](#setup-and-teardown) | ||
@@ -69,16 +65,15 @@ ## Class: `Suite` | ||
An `Suite` is responsible for managing and executing | ||
benchmark functions. It provides two methods: `add()` and `run()`. | ||
A `Suite` manages and executes benchmark functions. It provides two methods: `add()` and `run()`. | ||
### `new Suite([options])` | ||
* `options` {Object} Configuration options for the suite. The following | ||
properties are supported: | ||
* `reporter` {Function} Callback function with results to be called after | ||
benchmark is concluded. The callback function should receive two arguments: | ||
`suite` - A {Suite} object and | ||
`result` - A object containing three properties: | ||
`opsSec` {string}, `iterations {Number}`, `histogram` {Histogram} instance. | ||
* `options` {Object} Configuration options for the suite. Supported properties: | ||
* `reporter` {Function} Callback function for reporting results. Receives two arguments: | ||
* `suite` {Suite} The Suite instance. | ||
* `result` {Object} Contains: | ||
* `opsSec` {string} Operations per second. | ||
* `iterations` {Number} Number of iterations. | ||
* `histogram` {Histogram} Histogram instance. | ||
If no `reporter` is provided, the results will printed to the console. | ||
If no `reporter` is provided, results are printed to the console. | ||
@@ -92,20 +87,14 @@ ```js | ||
* `name` {string} The name of the benchmark, which is displayed when reporting | ||
benchmark results. | ||
* `options` {Object} Configuration options for the benchmark. The following | ||
properties are supported: | ||
* `minTime` {number} The minimum time a benchmark can run. | ||
**Default:** `0.05` seconds. | ||
* `maxTime` {number} The maximum time a benchmark can run. | ||
**Default:** `0.5` seconds. | ||
* `fn` {Function|AsyncFunction} | ||
* `name` {string} The name of the benchmark, displayed when reporting results. | ||
* `options` {Object} Configuration options for the benchmark. Supported properties: | ||
* `minTime` {number} Minimum duration for the benchmark to run. **Default:** `0.05` seconds. | ||
* `maxTime` {number} Maximum duration for the benchmark to run. **Default:** `0.5` seconds. | ||
* `fn` {Function|AsyncFunction} The benchmark function. Can be synchronous or asynchronous. | ||
* Returns: {Suite} | ||
This method stores the benchmark of a given function (`fn`). | ||
The `fn` parameter can be either an asynchronous (`async function () {}`) or | ||
a synchronous (`function () {}`) function. | ||
Adds a benchmark function to the suite. | ||
```console | ||
```bash | ||
$ node --allow-natives-syntax my-benchmark.js | ||
Using delete property x 5,853,505 ops/sec ± 0.01% (10 runs sampled) min..max=(169ns ... 171ns) p75=170ns p99=171ns | ||
Using delete property x 5,853,505 ops/sec ± 0.01% (10 runs sampled) min..max=(169ns ... 171ns) p75=170ns p99=171ns | ||
``` | ||
@@ -115,127 +104,31 @@ | ||
* Returns: `{Promise<Array<Object>>}` | ||
* `opsSec` {number} The amount of operations per second | ||
* `iterations` {number} The amount executions of `fn` | ||
* `histogram` {Histogram} Histogram object used to record benchmark iterations | ||
* `name` {string} Benchmark name | ||
* `plugins` {object} Object containing the plugin results if there's one active | ||
* Returns: `{Promise<Array<Object>>}` An array of benchmark results, each containing: | ||
* `opsSec` {number} Operations per second. | ||
* `iterations` {number} Number of executions of `fn`. | ||
* `histogram` {Histogram} Histogram of benchmark iterations. | ||
* `name` {string} Benchmark name. | ||
* `plugins` {Object} Object with plugin results if any plugins are active. | ||
The purpose of the run method is to run all the benchmarks that have been | ||
added to the suite using the [`suite.add()`][] function. | ||
By calling the run method, you can easily trigger the execution of all | ||
the stored benchmarks and obtain the corresponding results. | ||
Runs all added benchmarks and returns the results. | ||
## Plugins | ||
The benchmark module supports a flexible plugin system that | ||
allows you to extend its functionality by adding custom plugins. | ||
This documentation explains how to create, validate, and use | ||
plugins within the benchmarking framework. | ||
Plugins extend the functionality of the benchmark module. | ||
[V8NeverOptimizePlugin](#class-v8neveroptimizeplugin) is enabled by default. | ||
See [Plugins](./doc/Plugins.md) for details. | ||
### Structure | ||
Each plugin is expected to follow a specific structure with required methods | ||
for integration into the benchmark module. The plugins are required to define | ||
the following methods: | ||
* `isSupported()`: This method checks if the plugin can run in the | ||
current environment. If the plugin uses features specific to certain | ||
environments (e.g., V8 engine features), it should return `true` if those | ||
features are available and `false` otherwise. | ||
* `toString()`: This method should return a string representation of the plugin. | ||
It’s used for logging and error messages. | ||
In addition to these required methods, plugins can optionally define other | ||
methods based on their functionality, such as `beforeClockTemplate()`, | ||
`afterClockTemplate()`, `onCompleteBenchmark()`, and more. | ||
### Plugin Methods | ||
### `isSupported()` (required) | ||
- **`isSupported()`**: Checks if the plugin can run in the current environment. | ||
- **`beforeClockTemplate(varNames)`**: Injects code before the benchmark starts. Returns an array with: | ||
* `Code` {string} JavaScript code to execute. | ||
* `Wrapper` {string} (optional) Function to wrap the benchmark function. | ||
- **`afterClockTemplate(varNames)`**: Injects code after the benchmark finishes. Returns an array with: | ||
* `Code` {string} JavaScript code to execute. | ||
- **`onCompleteBenchmark(result)`**: Called when the benchmark completes, allowing plugins to process results. | ||
- **`toString()`**: Returns a string identifier for the plugin. | ||
This method checks if the plugin's functionality is available in the | ||
current environment. For instance, if a plugin uses specific V8 engine commands, | ||
this method ensures the environment supports them. | ||
### Example Plugin | ||
### `beforeClockTemplate(varNames)` | ||
* `varNames` {Object} | ||
* `bench` {string} - Name for the benchmark variable. | ||
* `context` {string} - Name for the context variable. | ||
* `timer` {string} - Name for the timer variable. | ||
* `awaitOrEmpty` {string} - A string with `await` or empty string (`''`). | ||
Some plugins need to modify or prepare the code before the benchmark starts. | ||
The `beforeClockTemplate()` method allows you to inject code before the timing | ||
process begins. | ||
This method must return an array where: | ||
* The first element is a string representing the JavaScript code to be executed | ||
before the benchmark function. | ||
* The second element (optional) is a string representing a function that will | ||
wrap the benchmark function. This wrapper is used to customize how the | ||
benchmark function is called during execution. | ||
The wrapped function provides a powerful way to manipulate how the benchmark | ||
is run without directly modifying the benchmark logic. | ||
```js | ||
beforeClockTemplate() { | ||
let code = ''; | ||
code += ` | ||
function DoNotOptimize(x) {} | ||
// Prevent DoNotOptimize from optimizing or being inlined. | ||
%NeverOptimizeFunction(DoNotOptimize); | ||
` | ||
return [code, 'DoNotOptimize']; | ||
} | ||
``` | ||
In this example, the plugin injects the `DoNotOptimize` function and also | ||
provides it as a wrapper for the benchmark function. | ||
### `afterClockTemplate(varNames)` | ||
* `varNames` {Object} | ||
* `bench` {string} - Name for the benchmark variable. | ||
* `context` {string} - Name for the context variable. | ||
* `timer` {string} - Name for the timer variable. | ||
* `awaitOrEmpty` {string} - A string with `await` or empty string (`''`). | ||
After the benchmark runs, this method can inject code to gather performance data | ||
or reset configurations. It must return an array where: | ||
* The first element is a string containing the JavaScript code to be executed | ||
after the benchmark finishes. | ||
Unlike `beforeClockTemplate`, `afterClockTemplate` does not support a second | ||
element in the returned array, as it only runs cleanup or data collection code | ||
after the benchmark is executed. | ||
### `onCompleteBenchmark(result)` | ||
* `result` {Object} | ||
* `duration` {number} - Benchmark duration | ||
* `count` {number} - Number of iterations | ||
* `context` {Object} - A object used to store results after the benchmark clock | ||
This method is called when the benchmark completes. Plugins can collect and | ||
process data from the benchmark results in this step. | ||
### `toString()` (required) | ||
This method returns a string identifier for the plugin, typically the plugin’s | ||
name. It is used in error messages and logging. | ||
### Example Plugins | ||
Here are examples of plugins that follow the required structure and functionality. | ||
```js | ||
class V8OptimizeOnNextCallPlugin { | ||
@@ -253,7 +146,5 @@ isSupported() { | ||
let code = ''; | ||
code += `%OptimizeFunctionOnNextCall(${ bench }.fn);\n`; | ||
code += `${ awaitOrEmpty }${ bench }.fn();\n`; | ||
code += `${ awaitOrEmpty }${ bench }.fn();\n`; | ||
code += `%OptimizeFunctionOnNextCall(${bench}.fn);\n`; | ||
code += `${awaitOrEmpty}${bench}.fn();\n`; | ||
code += `${awaitOrEmpty}${bench}.fn();\n`; | ||
return [code]; | ||
@@ -268,40 +159,14 @@ } | ||
## Official Plugins | ||
## Using Custom Reporter | ||
This is a list of official plugins that can be fetched when requiring | ||
`bench-node` module. | ||
Customize data reporting by providing a `reporter` function when creating the `Suite`: | ||
```js | ||
const { V8OptimizeOnNextCallPlugin, Suite } = require('bench-node'); | ||
const suite = new Suite({ | ||
plugins: [new V8OptimizeOnNextCallPlugin()], | ||
}) | ||
``` | ||
### Class: `V8OptimizeOnNextCallPlugin` | ||
The `V8OptimizeOnNextCallPlugin` triggers the V8 engine to optimize the | ||
function before it is called. This can improve performance in repeated | ||
benchmarks. | ||
### Class: `V8NeverOptimizePlugin` | ||
The `V8NeverOptimizePlugin` prevents the V8 engine from optimizing or inlining | ||
a function, useful when you want to benchmark functions without any | ||
optimization. | ||
### Class: `V8GetOptimizationStatus` | ||
The `V8GetOptimizationStatus` plugin collects the V8 engine's optimization | ||
status for a given function after it has been benchmarked. | ||
## Using custom reporter | ||
You can customize the data reporting by passing an function to the `reporter` argument while creating your `Suite`: | ||
```js | ||
const { Suite } = require('bench-node'); | ||
function reporter(bench, result) { | ||
console.log(`Benchmark: ${bench.name} - ${result.opsSec} ops/sec`); | ||
console.log(`Benchmark: ${bench.name}`); | ||
console.log(`Operations per second: ${result.opsSec}`); | ||
console.log(`Iterations: ${result.iterations}`); | ||
console.log(`Histogram: ${result.histogram}`); | ||
} | ||
@@ -323,5 +188,5 @@ | ||
```console | ||
```bash | ||
$ node --allow-natives-syntax my-benchmark.js | ||
Benchmark: Using delete to remove property from object - 6032212 ops/sec | ||
Benchmark: Using delete to remove property from object - 6,032,212 ops/sec | ||
``` | ||
@@ -331,6 +196,5 @@ | ||
The benchmark function has a special handling when you pass an argument, | ||
for example: | ||
Control the benchmark function's setup and teardown using the timer argument: | ||
```cjs | ||
```js | ||
const { Suite } = require('bench-node'); | ||
@@ -354,10 +218,4 @@ const { readFileSync, writeFileSync, rmSync } = require('node:fs'); | ||
In this way, you can control when the `timer` will start | ||
and also when the `timer` will stop. | ||
For advanced setups, use the timer argument to start and end timing explicitly: | ||
In the timer, we also give you a property `count` | ||
that will tell you how much iterations | ||
you should run your function to achieve the `benchmark.minTime`, | ||
see the following example: | ||
```js | ||
@@ -375,7 +233,5 @@ const { Suite } = require('bench-node'); | ||
timer.start(); | ||
for (let i = 0; i < timer.count; i++) | ||
for (let i = 0; i < timer.count; i++) { | ||
readFileSync(filePath, 'utf8'); | ||
// You must send to the `.end` function the amount of | ||
// times you executed the function, by default, | ||
// the end will be called with value 1. | ||
} | ||
timer.end(timer.count); | ||
@@ -389,4 +245,2 @@ | ||
Once your function has at least one argument, | ||
you must call `.start` and `.end`, if you didn't, | ||
it will throw the error `ERR_BENCHMARK_MISSING_OPERATION` | ||
Ensure you call `.start()` and `.end()` methods when using the timer argument, or an `ERR_BENCHMARK_MISSING_OPERATION` error will be thrown. |
@@ -172,5 +172,1 @@ const { Suite } = require('../lib/index'); | ||
}); | ||
todo('async tasks should behave similar to sync tasks', async () => { | ||
}); |
61385
38
1489
235