[![npm package][npm-img]][npm-url]
[![Build Status][build-img]][build-url]
[![Downloads][downloads-img]][downloads-url]
[![Issues][issues-img]][issues-url]
The `bench-node` module allows you to measure operations per second of Node.js code blocks.
## Install
```bash
$ npm install bench-node
```
## Usage
```cjs
const { Suite } = require('bench-node');
const suite = new Suite();
suite.add('Using delete property', () => {
const data = { x: 1, y: 2, z: 3 };
delete data.y;
data.x;
data.y;
data.z;
});
suite.run()
```
```bash
$ node --allow-natives-syntax my-benchmark.js
Using delete property x 3,326,913 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(0ns ... 0ns) p75=0ns p99=0ns
```
This module uses V8 deoptimization to help ensure that the code block is not optimized away, producing accurate benchmarks -- but not realistic.
See the [Writing JavaScript Microbenchmark Mistakes](#writing-javascript-mistakes) section for more details.
The [`bench-node-cli`](https://github.com/RafaelGSS/bench-node-cli) tool allows you to execute a `bench-node` benchmark
from any location, eliminating the need to install the `bench-node` package locally.
Simply use the following command to run your benchmark:
```bash
npx bench-node-cli my-benchmark.js
```
See the [examples folder](./examples/) for more common usage examples.
## Table of Contents
- [Install](#install)
- [Usage](#usage)
- [Table of Contents](#table-of-contents)
- [Sponsors](#sponsors)
- [Class: `Suite`](#class-suite)
- [`new Suite([options])`](#new-suiteoptions)
- [`suite.add(name[, options], fn)`](#suiteaddname-options-fn)
- [`suite.run()`](#suiterun)
- [Dead Code Elimination Detection](#dead-code-elimination-detection)
- [How It Works](#how-it-works)
- [Configuration](#configuration)
- [When DCE Warnings Appear](#when-dce-warnings-appear)
- [Plugins](#plugins)
- [Plugin Methods](#plugin-methods)
- [Example Plugin](#example-plugin)
- [Using Reporter](#using-reporter)
- [`textReport` (Default)](#textreport-default)
- [`chartReport`](#chartreport)
- [`htmlReport`](#htmlreport)
- [`jsonReport`](#jsonreport)
- [CSV Reporter](#csv-reporter)
- [Pretty Reporter](#pretty-reporter)
- [Custom Reporter](#custom-reporter)
- [Setup and Teardown](#setup-and-teardown)
- [Managed Benchmarks](#managed-benchmarks)
- [Worker Threads](#worker-threads)
- [Benchmark Modes](#benchmark-modes)
- [Operations Mode](#operations-mode)
- [Time Mode](#time-mode)
- [Baseline Comparisons](#baseline-comparisons)
- [Statistical Significance Testing](#statistical-significance-testing-t-test)
- [Direct API Usage](#direct-api-usage)
- [Fixing Inconclusive Tests](#fixing-inconclusive-tests)
- [Writing JavaScript Mistakes](#writing-javascript-mistakes)
## Sponsors
Test machines are generously sponsored by [NodeSource](https://nodesource.com/).
## Class: `Suite`
> Stability: 1.1 Active Development
A `Suite` manages and executes benchmark functions. It provides two methods: `add()` and `run()`.
### `new Suite([options])`
* `options` {Object} Configuration options for the suite.
* `reporter` {Function} Callback function for reporting results. Receives two arguments:
* `results` {Object[]} Array of benchmark results:
* `name` {string} Benchmark name.
* `opsSec` {string} Operations per second.
* `iterations` {Number} Number of iterations.
* `histogram` {Histogram} Histogram instance.
* `ttest` {boolean} Enable Welch's t-test for statistical significance testing. Automatically sets `repeatSuite=30`. **Default:** `false`.
* `reporterOptions` {Object} Reporter-specific options.
* `printHeader` {boolean} Whether to print system information header. **Default:** `true`.
* `labelWidth` {number} Width for benchmark labels in output. **Default:** `45`.
* `alpha` {number} Significance level for t-test (e.g., 0.05 for 95% confidence). **Default:** `0.05`.
* `benchmarkMode` {string} Benchmark mode to use. Can be 'ops' or 'time'. **Default:** `'ops'`.
* `'ops'` - Measures operations per second (traditional benchmarking).
* `'time'` - Measures actual execution time for a single run.
* `useWorkers` {boolean} Whether to run benchmarks in worker threads. **Default:** `false`.
* `plugins` {Array} Array of plugin instances to use.
* `repeatSuite` {number} Number of times to repeat each benchmark. Automatically set to `30` when `ttest: true`. **Default:** `1`.
* `plugins` {Array} Array of plugin instances to use. **Default:** `[V8NeverOptimizePlugin]`.
* `minSamples` {number} Minimum number of samples per round for all benchmarks in the suite. Can be overridden per benchmark. **Default:** `10` samples.
* `detectDeadCodeElimination` {boolean} Enable dead code elimination detection. When enabled, default plugins are disabled to allow V8 optimizations. **Default:** `false`.
* `dceThreshold` {number} Threshold multiplier for DCE detection. Benchmarks faster than baseline × threshold will trigger warnings. **Default:** `10`.
If no `reporter` is provided, results are printed to the console.
```js
const { Suite } = require('bench-node');
const suite = new Suite();
```
If you don't want results to be printed to the console, `false` and `null` can be used
```js
const { Suite } = require('bench-node');
const suite = new Suite({ reporter: false });
```
### `suite.add(name[, options], fn)`
* `name` {string} The name of the benchmark, displayed when reporting results.
* `options` {Object} Configuration options for the benchmark. Supported properties:
* `minTime` {number} The minimum duration of each sampling interval. **Default:** `0.05` seconds.
* `maxTime` {number} Maximum duration for the benchmark to run. **Default:** `0.5` seconds.
* `repeatSuite` {number} Number of times to repeat benchmark to run. **Default:** `1` times.
* `minSamples` {number} Number minimum of samples the each round. **Default:** `10` samples.
* `baseline` {boolean} Mark this benchmark as the baseline for comparison. Only one benchmark per suite can be baseline. **Default:** `false`.
* `fn` {Function|AsyncFunction} The benchmark function. Can be synchronous or asynchronous.
* Returns: {Suite}
Adds a benchmark function to the suite.
```bash
$ node --allow-natives-syntax my-benchmark.js
Using delete property x 5,853,505 ops/sec (10 runs sampled) min..max=(169ns ... 171ns)
```
### `suite.run()`
* Returns: `{Promise>}` An array of benchmark results, each containing:
* `opsSec` {number} Operations per second (Only in 'ops' mode).
* `opsSecPerRun` {Array} Array of operations per second (useful when repeatSuite > 1).
* `totalTime` {number} Total execution time in seconds (Only in 'time' mode).
* `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.
Runs all added benchmarks and returns the results.
## Dead Code Elimination Detection
**bench-node** includes optional detection of dead code elimination (DCE) to help identify benchmarks that may be producing inaccurate results. When the JIT compiler optimizes away your benchmark code, it can run nearly as fast as an empty function, leading to misleading performance measurements.
**Important:** DCE detection is **opt-in**. When enabled, the `V8NeverOptimizePlugin` is automatically disabled to allow V8 optimizations to occur naturally. This helps catch benchmarks that would be optimized away in real-world scenarios.
### How It Works
When enabled, bench-node measures a baseline (empty function) performance before running your benchmarks. After each benchmark completes, it compares the timing against this baseline. If a benchmark runs suspiciously fast (less than 10× slower than the baseline by default), a warning is emitted.
### Example Warning Output
```
⚠️ Dead Code Elimination Warnings:
The following benchmarks may have been optimized away by the JIT compiler:
• array creation
Benchmark: 3.98ns/iter
Baseline: 0.77ns/iter
Ratio: 5.18x of baseline
Suggestion: Ensure the result is used or assign to a variable
ℹ️ These benchmarks are running nearly as fast as an empty function,
which suggests the JIT may have eliminated the actual work.
```
### Configuration
```js
const { Suite, V8NeverOptimizePlugin } = require('bench-node');
// Enable DCE detection (disables V8NeverOptimizePlugin automatically)
const suite = new Suite({
detectDeadCodeElimination: true
});
// Enable DCE detection with custom threshold (default is 10x)
const strictSuite = new Suite({
detectDeadCodeElimination: true,
dceThreshold: 20 // Only warn if < 20x slower than baseline
});
// Use both DCE detection AND prevent optimization
// (helpful for educational purposes to see warnings even when using %NeverOptimizeFunction)
const educationalSuite = new Suite({
plugins: [new V8NeverOptimizePlugin()],
detectDeadCodeElimination: true
});
```
### When DCE Warnings Appear
Common scenarios that trigger warnings:
```js
// ❌ Result not used - will be optimized away
suite.add('computation', () => {
const result = Math.sqrt(144);
// result is never used
});
// ✅ Result is used - less likely to be optimized
suite.add('computation', () => {
const result = Math.sqrt(144);
if (result !== 12) throw new Error('Unexpected');
});
```
**Note:** DCE detection only works in `'ops'` benchmark mode and when not using worker threads. It is automatically disabled for `'time'` mode and worker-based benchmarks.
See [examples/dce-detection/](./examples/dce-detection/) for more examples.
## Plugins
Plugins extend the functionality of the benchmark module.
See [Plugins](./doc/Plugins.md) for details.
### Plugin Methods
- **`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, bench)`**: Called when the benchmark completes, allowing plugins to process results.
- **`toString()`**: Returns a string identifier for the plugin.
- **`getReport(benchmarkName)`**: Returns a string to be displayed in the benchmark result line.
- **`getResult(benchmarkName)`**: Returns the data that can be used by the reporter.
- **`reset()`**: Resets the plugin state, to avoid carrying over data between benchmarks.
### Example Plugin
```js
class V8OptimizeOnNextCallPlugin {
isSupported() {
try {
new Function(`%OptimizeFunctionOnNextCall(() => {})`)();
return true;
} catch (e) {
return false;
}
}
beforeClockTemplate({ awaitOrEmpty, bench }) {
let code = '';
code += `%OptimizeFunctionOnNextCall(${bench}.fn);\n`;
code += `${awaitOrEmpty}${bench}.fn();\n`;
code += `${awaitOrEmpty}${bench}.fn();\n`;
return [code];
}
toString() {
return 'V8OptimizeOnNextCallPlugin';
}
}
```
## Using Reporter
This module exports two reporters that control how benchmark results are displayed:
a detailed `textReport` for statistical analysis, and a visual `chartReport` that
displays a bar graph in the terminal.
### `textReport` (Default)
The `textReport` is the default reporter, which provides simple statistical information
about each benchmark result. It includes the number of operations per second, the number
of runs sampled, min...max, and enabled plugins.
**Example Output**:
```
single with matcher x 710,248 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(1.40us...1.42us)
multiple replaces x 604,713 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(1.64us...1.70us)
```
Here’s how you can explicitly pass it as a reporter:
```cjs
const { Suite, textReport } = require('bench-node');
const suite = new Suite({
reporter: textReport, // Optional, since this is the default
});
```
### `chartReport`
The `chartReport` reporter provides a graphical representation of benchmark
results in the form of a bar chart, making it easier to visualize the relative
performance of each benchmark. It scales the bars based on the highest operations
per second (ops/sec) value, and displays the results incrementally as they are collected.
Example output:
```
Node.js version: v23.6.1
Platform: darwin arm64
CPU Cores: 8 vCPUs | 16.0GB Mem
single with matcher | ██████████████████████████████ | 709,321 ops/sec | 10 samples
multiple replaces | ██████████████████████████---- | 606,401 ops/sec | 11 samples
```
Usage:
```cjs
const { Suite, chartReport } = require('bench-node');
const suite = new Suite({
reporter: chartReport,
// Optionally control header display
reporterOptions: {
printHeader: true // Set to false to hide system info header
}
});
```
The `reporterOptions.printHeader` setting controls whether the system information (Node.js version, platform, and CPU cores) appears at the top of the output. This is useful when running multiple suites in sequence and you want to avoid duplicate headers.
### `htmlReport`
The `htmlReport` generates an interactive HTML visualization of benchmark results.
It transforms benchmark data into a visual format, such as speed circle animations,
making it easier to interpret and share performance insights.
Example output:
https://github.com/user-attachments/assets/b2b98175-6648-4af4-8319-63f3ebbc729e
Usage:
```cjs
const { Suite, htmlReport } = require('bench-node');
const suite = new Suite({
reporter: htmlReport,
});
```
### `jsonReport`
The `jsonReport` plugin provides benchmark results in **JSON format**.
It includes key performance metrics—such as `opsSec`, `runsSampled`, `min`
and `max` times, and any reporter data from your **plugins**—so you can easily
store, parse, or share the information.
Example output:
```json
[
{
"name": "single with matcher",
"opsSec": 180000,
"runsSampled": 50,
"min": "13.20μs",
"max": "82.57μs",
"plugins": []
},
{
"name": "Multiple replaces",
"opsSec": 170000,
"runsSampled": 50,
"min": "15.31μs",
"max": "77.49μs",
"plugins": []
}
]
```
**Usage:**
```cjs
const { Suite, jsonReport } = require('bench-node');
const suite = new Suite({
reporter: jsonReport,
});
```
### CSV Reporter
The `csvReport` plugin generates benchmark results in CSV format.
It includes columns for key performance metrics like `ops/sec`, `samples`, `min` and `max` times,
as well as any reporter data provided by your plugins.
Example output:
```csv
name,ops/sec,samples,plugins,min,max
Using delete property,"7,732,517",10,v8-never-optimize=true,127.91ns,129.95ns
Using delete property (proto: null),"24,636,631",10,v8-never-optimize=true,39.57ns,40.91ns
Using delete property (cached proto: null),"7,497,893",11,v8-never-optimize=true,132.25ns,134.89ns
Using undefined assignment,"132,093,600",11,v8-never-optimize=true,7.53ns,7.64ns
Using undefined assignment (proto: null),"28,231,374",9,v8-never-optimize=true,35.27ns,35.42ns
Using undefined property (cached proto: null),"60,843,193",10,v8-never-optimize=true,16.24ns,16.65ns
[Managed] Using undefined property (cached proto: null),"35,394,060",10,v8-never-optimize=true,27.90ns,28.54ns
```
**Usage:**
```cjs
const { Suite, csvReport } = require('bench-node');
const suite = new Suite({
reporter: csvReport,
});
```
### Pretty Reporter
The `prettyReport` provides a beautiful, hierarchical view of your benchmark results.
**Usage:**
```javascript
const { Suite } = require('bench-node');
// You can either pass the reporter function directly...
const suiteWithReporter = new Suite({
reporter: prettyReport,
});
// ...or use the `pretty` option for convenience.
const suiteWithPrettyOption = new Suite({
pretty: true,
});
suite
.add('my-group/my-benchmark', () => {
//...
})
.add('my-group/my-benchmark-2', () => {
//...
})
.add('second-group/baseline', () => {
//...
})
.run();
```
**Sample Output:**
```
System Information:
Node.js: v22.15.0
OS: darwin 24.5.0
CPU: Apple M2
Benchmark results (3 total):
Plugins enabled: V8NeverOptimizePlugin
├─ my-group
│ ├─ my-benchmark 1,461,380 ops/sec (10 runs sampled) min..max=(682.30ns...685.79ns)
│ └─ my-benchmark-2 2,270,973 ops/sec (10 runs sampled) min..max=(437.22ns...444.12ns)
└─ second-group
└─ baseline 637,787 ops/sec (11 runs sampled) min..max=(1.54us...1.59us)
```
### Custom Reporter
Customize data reporting by providing a `reporter` function when creating the `Suite`:
```js
const { Suite } = require('bench-node');
function reporter(results) {
for (const result of results) {
console.log(`Benchmark: ${result.name}`);
console.log(`Operations per second: ${result.opsSec}`);
console.log(`Iterations: ${result.iterations}`);
console.log(`Histogram: ${result.histogram}`);
}
}
const suite = new Suite({ reporter });
suite.add('Using delete to remove property from object', () => {
const data = { x: 1, y: 2, z: 3 };
delete data.y;
data.x;
data.y;
data.z;
});
suite.run();
```
```bash
$ node --allow-natives-syntax my-benchmark.js
Benchmark: Using delete to remove property from object - 6,032,212 ops/sec
```
## Setup and Teardown
Control the benchmark function's setup and teardown using the timer argument:
```js
const { Suite } = require('bench-node');
const { readFileSync, writeFileSync, rmSync } = require('node:fs');
const suite = new Suite();
suite.add('readFileSync', (timer) => {
const randomFile = Date.now();
const filePath = `./${randomFile}.txt`;
writeFileSync(filePath, Math.random().toString());
timer.start();
readFileSync(filePath, 'utf8');
timer.end();
rmSync(filePath);
}).run();
```
For advanced setups, use the timer argument to start and end timing explicitly:
```js
const { Suite } = require('bench-node');
const { readFileSync, writeFileSync, rmSync } = require('node:fs');
const suite = new Suite();
suite.add('readFileSync', (timer) => {
const randomFile = Date.now();
const filePath = `./${randomFile}.txt`;
writeFileSync(filePath, Math.random().toString());
timer.start();
for (let i = 0; i < timer.count; i++) {
readFileSync(filePath, 'utf8');
}
timer.end(timer.count);
rmSync(filePath);
});
suite.run();
```
> [!WARNING]
> When using the `timer`, the setup will also be deoptimized.
> As a result, if you compare this approach with one that uses functions outside
> the benchmark function, the results may not match.
> See: [Deleting Properties Example](./examples/deleting-properties/node.js).
Ensure you call `.start()` and `.end()` methods when using the timer argument, or an `ERR_BENCHMARK_MISSING_OPERATION` error will be thrown.
### Managed Benchmarks
In regular benchmarks (when `timer` is not used), you run the benchmarked function in a loop,
and the timing is managed implicitly.
This means each iteration of the benchmarked function is measured directly.
The downside is that optimizations like inlining or caching might affect the timing, especially for fast operations.
Example:
```cjs
suite.add('Using includes', () => {
const text = 'text/html,...';
const r = text.includes('application/json');
});
```
Here, `DoNotOptimize` is called inside the generated loop for regular benchmarks
(assuming `V8NeverOptimizePlugin` is being used), and the benchmark function is
also marked with `%NeverOptimizeFunction`.
Together, these prevent V8 from optimizing the benchmark function and from
treating the returned value as irrelevant.
Managed benchmarks explicitly handle timing through `start()` and `end()` calls around the benchmarked code.
This encapsulates the entire set of iterations in one timed block,
which can result in tighter measurement with less overhead.
However, it can lead to over-optimistic results, especially if the timer’s start and stop calls are placed outside of the loop,
allowing V8 to over-optimize the entire block.
Example:
```cjs
suite.add('[Managed] Using includes', (timer) => {
timer.start();
for (let i = 0; i < timer.count; i++) {
const text = 'text/html,...';
const r = text.includes('application/json');
assert.ok(r); // Ensure the result is used so it doesn't get V8 optimized away
}
timer.end(timer.count);
});
```
In this case, `DoNotOptimize` is applied to the benchmark function's return value,
outside the user-managed loop. It does not consume the local `r` value from each
iteration. That's why `assert.ok(r)` has been used: it makes the per-iteration
result observable inside the timed block.
> [!NOTE]
> V8 assumptions can change any time soon. Therefore, it's crucial to investigate
> results between versions of V8/Node.js.
### Worker Threads
> Stability: 1.0 (Experimental)
`bench-node` provides experimental support for **Worker Threads**. When you set `useWorkers: true`,
the library runs each benchmark in a separate worker thread, ensuring that one benchmark
does not affect another. Usage is straightforward:
```cjs
const suite = new Suite({
useWorkers: true,
});
```
## Benchmark Modes
`bench-node` supports multiple benchmarking modes to measure code performance in different ways.
### Operations Mode
Operations mode (default) measures how many operations can be performed in a given timeframe.
This is the traditional benchmarking approach that reports results in operations per second (ops/sec).
This mode is best for:
- Comparing relative performance between different implementations
- Measuring throughput of small, fast operations
- Traditional microbenchmarking
Example output:
```
String concatenation x 12,345,678 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(81ns...85ns)
```
### Time Mode
Time mode measures the actual time taken to execute a function exactly once.
This mode is useful when you want to measure the real execution time for operations that have a known, fixed duration.
This mode is best for:
- Costly operations where multiple instructions are executed in a single run
- Benchmarking operations with predictable timing
- Verifying performance guarantees for time-sensitive functions
To use time mode, set the `benchmarkMode` option to `'time'` when creating a Suite:
```js
const { Suite } = require('bench-node');
const timeSuite = new Suite({
benchmarkMode: 'time' // Enable time mode
});
// Create a function that takes a predictable amount of time
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
timeSuite.add('Async Delay 100ms', async () => {
await delay(100);
});
timeSuite.add('Sync Busy Wait 50ms', () => {
const start = Date.now();
while (Date.now() - start < 50);
});
// Optional: Run the benchmark multiple times with repeatSuite
timeSuite.add('Quick Operation with 5 repeats', { repeatSuite: 5 }, () => {
// This will run exactly once per repeat (5 times total)
// and report the average time
let x = 1 + 1;
});
(async () => {
await timeSuite.run();
})();
```
In time mode, results include `totalTime` (in seconds) instead of `opsSec`.
Example output:
```
Async Delay 100ms x 0.1003s (1 sample) v8-never-optimize=true
Sync Busy Wait 50ms x 0.0502s (1 sample) v8-never-optimize=true
Quick Operation with 5 repeats x 0.0000s (5 samples) v8-never-optimize=true
```
See [examples/time-mode.js](./examples/time-mode.js) for a complete example.
## Baseline Comparisons
You can mark one benchmark as a baseline to compare all other benchmarks against it:
```js
const { Suite } = require('bench-node');
const suite = new Suite();
suite
.add('baseline', { baseline: true }, () => {
// baseline implementation
const arr = [1, 2, 3];
arr.includes(2);
})
.add('alternative', () => {
// alternative implementation
const arr = [1, 2, 3];
arr.indexOf(2) !== -1;
});
suite.run();
```
Example output with baseline:
```
baseline x 52,832,865 ops/sec (10 runs sampled) min..max=(18.50ns...19.22ns)
alternative x 53,550,219 ops/sec (11 runs sampled) min..max=(18.26ns...18.89ns)
Summary (vs. baseline):
baseline (baseline)
alternative (1.01x faster)
```
## Statistical Significance Testing (T-Test)
> Stability: 1.0 (Experimental)
When comparing benchmarks, especially on machines with high variance (cloud VMs, shared environments),
raw ops/sec differences may not be meaningful. `bench-node` provides **Welch's t-test** to determine
if performance differences are statistically significant.
Welch's t-test is preferred over Student's t-test because it doesn't assume equal variances between
the two samples, which is common in benchmark scenarios.
### Enabling T-Test Mode
Enable t-test mode with `ttest: true`. This automatically sets `repeatSuite=30` to collect enough
independent samples for reliable statistical analysis (per the Central Limit Theorem):
```js
const { Suite } = require('bench-node');
const suite = new Suite({
ttest: true, // Enables t-test and auto-sets repeatSuite=30
});
suite
.add('baseline', { baseline: true }, () => {
let sum = 0;
for (let i = 0; i < 100; i++) sum += i;
})
.add('optimized', () => {
let sum = (99 * 100) / 2; // Gauss formula
});
suite.run();
```
Example output:
```
T-Test Mode: Enabled (repeatSuite=30)
baseline x 1,234,567 ops/sec (300 runs sampled) min..max=(810.05ns...812.45ns)
optimized x 9,876,543 ops/sec (305 runs sampled) min..max=(101.23ns...102.87ns)
Summary (vs. baseline):
baseline (baseline)
optimized (8.00x faster) ***
Significance: * p<0.05, ** p<0.01, *** p<0.001
```
The asterisks indicate significance level:
- `***` = p < 0.001 (0.1% risk of false positive)
- `**` = p < 0.01 (1% risk of false positive)
- `*` = p < 0.05 (5% risk of false positive)
- (no stars) = not statistically significant
This helps identify when a benchmark shows a difference due to random variance vs. a real performance improvement.
**How it works**: With `ttest: true`, each benchmark runs 30 times independently (via `repeatSuite=30`). The t-test compares the 30 ops/sec values from the baseline against the 30 ops/sec values from each test benchmark. This accounts for run-to-run variance within that benchmark session.
**Note**: Running the entire benchmark suite multiple times may still show variance in absolute numbers due to system-level factors (CPU frequency scaling, thermal throttling, background processes). The t-test helps determine if differences are statistically significant within each benchmark session, but results can vary between separate benchmark runs due to changing system conditions.
See also: [Fixing Inconclusive Tests](doc/Inconclusive.md).
### Direct API Usage
You can also use the t-test utilities directly for custom analysis:
```js
const { welchTTest, compareBenchmarks } = require('bench-node');
// Raw sample data from two benchmarks (e.g., timing samples in nanoseconds)
const baseline = [100, 102, 99, 101, 100, 98, 103, 99, 100, 101];
const optimized = [50, 51, 49, 52, 50, 48, 51, 49, 50, 51];
// High-level comparison
const result = compareBenchmarks(optimized, baseline, 0.05);
console.log(result);
// {
// significant: true,
// pValue: 0.00001,
// confidence: '99.99%',
// stars: '***',
// difference: 'faster',
// tStatistic: 45.2,
// degreesOfFreedom: 17.8
// }
// Low-level Welch's t-test
const ttest = welchTTest(optimized, baseline);
console.log(ttest);
// {
// tStatistic: 45.2,
// degreesOfFreedom: 17.8,
// pValue: 0.00001,
// significant: true,
// mean1: 50.1,
// mean2: 100.3,
// variance1: 1.43,
// variance2: 2.23
// }
```
#### Interpreting Results
- **`significant: true`** - The performance difference is statistically significant at the given alpha level
- **`pValue`** - Probability that the observed difference occurred by chance (lower = more confident)
- **`confidence`** - Confidence level (e.g., "99.95%" means 99.95% confident the difference is real)
- **`stars`** - Visual indicator of significance: `'***'` (p<0.001), `'**'` (p<0.01), `'*'` (p<0.05), or `''` (not significant)
- **`difference`** - Whether the first sample is `'faster'`, `'slower'`, or `'same'` as the second
A common threshold is `alpha = 0.05` (95% confidence). If `pValue < alpha`, the difference is significant.
## Writing JavaScript Mistakes
When working on JavaScript micro-benchmarks, it’s easy to forget that modern engines use
multiple tiers of Just-In-Time (JIT) compilation and sometimes even entirely different
optimizations. The results you get from a simple timing loop often aren’t representative
of how your code will behave under real-world conditions, especially once the browser or
runtime has adjusted for frequent function calls. Caching, tail call optimizations,
and hidden class transformations can all distort your measurements, leading to overblown
claims about performance improvements that might never materialize in production.
That’s why **bench-node** was created—to provide a stable and consistent way to compare
small snippets of code. By default, it tells V8 to never optimize the benchmark
function with `%NeverOptimizeFunction(bench.fn)` and consumes the returned value
with a non-optimized `DoNotOptimize` helper so the JIT compiler doesn’t remove
dead code. However, even this approach can’t fully replicate real-world scenarios
in which V8 optimizations and unpredictable workloads impact performance. Think of
bench-node as a helpful tool for quick comparisons rather than a guarantee of what you’ll
see in production.
[build-img]:https://github.com/RafaelGSS/bench-node/actions/workflows/release.yml/badge.svg
[build-url]:https://github.com/RafaelGSS/bench-node/actions/workflows/release.yml
[downloads-img]:https://img.shields.io/npm/dt/bench-node
[downloads-url]:https://www.npmtrends.com/bench-node
[npm-img]:https://img.shields.io/npm/v/bench-node
[npm-url]:https://www.npmjs.com/package/bench-node
[issues-img]:https://img.shields.io/github/issues/RafaelGSS/bench-node
[issues-url]:https://github.com/RafaelGSS/bench-node/issues
================================================
FILE: assets/README.md
================================================
# "Branding"
## Logo
| Logo | Description |
| --- | --- |
|  | simple logo for readme |
|  | logo with text for light backgrounds |
|  | logo with text for dark backgrounds |
## Other
- **Font**: [Geist](https://fonts.google.com/specimen/Geist)
- **Light Green**: `#84BA64`
- **Dark Green**: `#2C682C`
> The design of the assets was created by [@AugustinMauroy](https://github.com/AugustinMauroy).
================================================
FILE: biome.json
================================================
{
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": {
"noExplicitAny": "off"
},
"style": {
"noParameterAssign": "off",
"noUselessElse": "off"
}
}
},
"formatter": {
"enabled": true
},
"files": {
"ignore": [
"examples/*",
"coverage/*",
"test/plugin-api-doc.js",
"package.json"
]
}
}
================================================
FILE: doc/Inconclusive.md
================================================
# Fixing Inconclusive Tests
t-tests are looking at the distribution of both sets of results and trying to determine if they overlap in a way that
makes the average value significant or just noise in the results. A run with a bimodal distribution for instance, caused
by problems with the machine the tests are running on or the NodeJS runtime doing things in the background. Here are a
few causes.
## Random Input Data
Variability in the inputs between runs can lead to big changes in the runtime of an algorithm. Particularly with code
that sorts, filters, or conditionally operates on input data, feeding them certain combinations of data will result in
wildly different run times from one loop to the next or occasionally from one sample to the next. The Central Limit
Theorem (that over a long enough time a situation will revert to the mean), does not invalidate the existence of the
Gambler's Paradox (that it will revert to the mean before I become bankrupt).
It is better to do your fuzzing in fuzz tests and pick representative data for your benchmarks. Partially informed by
the results of your fuzz tests, and other bug reports.
## Underprovisioned VM, Oversubscribed hardware
For a clean micro benchmark, we generally want to be the only one using the machine at the time. There are a number of
known issues running benchmarks on machines that are thermally throttling, or on cheap VMs that use best-effort to
allocate CPU time to the running processes. In particular, docker images with `cpu-shares` are especially poor targets
for running benchmarks because the quota might expire for one timeslice in the middle of one test or between benchmarks
in a single Suite. This creates an unfair advantage for the first test, and/or lots of noise in the results. We are
currently investigating ways to detect this sort of noise, and analyzing if the t-tests are sufficient to do so.
## Epicycles in GC or JIT compilation
If the warmup time is insufficient to get V8 to optimize the code, it may kick in during the middle of a sample, which
will introduce a bimodal distribution of answers (before, and after). There is currently not a way to adjust the warmup
time of `bench-node`, but should be added as a feature.
One of the nastiest performance issues to detect in garbage collected code is allocation epicycles. This happens when
early parts of a calculation create lots of temporary data but not sufficient to cross the incremental or full GC
threshold, so that the next function in a call sequence routinely gets hit with exceeding the threshold. This is
especially common in code that generates a JSON or HTML response to a series of calculations - it is the single biggest
allocation in the sequence, but it gets blamed in the performance report for the lion's share of the CPU time.
If you change the `minTime` up or down, that will alter the number of iterations per sample which may smooth out the
results. You can also try increasing `minSamples` to get more samples. But also take this as a suggestion that your code
may have a performance bug that is worth prioritizing.
Forcing GC in the teardown or setup methods between subsequent tests in a single Suite may help with some situations
when reordering the tests results in differences in runtime, but for the more general case, you may need to review the
code under test to make it 'play well' both with benchmarking and in production systems.
In production code, particularly where p9# values are used as a fitness test, it is sometimes better to chose the
algorithm with more consistent runtime over the one with supposedly better average runtime. This can also be true where
DDOS scenarios are possible - the attacker will always chose the worst, most assymetric request to send to your machine,
and mean response time will not matter one whit. If `bench-node` is complaining, the problem may not be `bench-node`.
================================================
FILE: doc/Plugins.md
================================================
# 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.
[V8NeverOptimizePlugin](#class-v8neveroptimizeplugin) is enabled by default.
To observe how a plugin is used, see the `plugin-api-doc.js` file in tests and explore its results.
## 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)
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.
### `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({ bench }) {
let code = '';
code += `
function DoNotOptimize(x) {}
// Prevent the benchmark function and result consumer from optimizing or being inlined.
%NeverOptimizeFunction(${bench}.fn);
%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 result. The benchmark
function itself is marked with `%NeverOptimizeFunction(${bench}.fn)`, while the
`DoNotOptimize` wrapper consumes the returned value so the benchmark expression
does not become observationally irrelevant.
These two protections address different parts of the generated code:
`%NeverOptimizeFunction(${bench}.fn)` targets the function under test, and
`DoNotOptimize(bench.fn())` targets the value returned by each call.
### `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 {
isSupported() {
try {
new Function(`%OptimizeFunctionOnNextCall(() => {})`)();
return true;
} catch (e) {
return false;
}
}
beforeClockTemplate({ awaitOrEmpty, bench }) {
let code = '';
code += `%OptimizeFunctionOnNextCall(${ bench }.fn);\n`;
code += `${ awaitOrEmpty }${ bench }.fn();\n`;
code += `${ awaitOrEmpty }${ bench }.fn();\n`;
return [code];
}
toString() {
return 'V8OptimizeOnNextCallPlugin';
}
}
```
## Official Plugins
This is a list of official plugins that can be fetched when requiring
`bench-node` module.
```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
the benchmark function. It also wraps the benchmark result in a non-optimized
`DoNotOptimize` helper so V8 cannot treat an unused return value as irrelevant.
### Class: `V8GetOptimizationStatus`
The `V8GetOptimizationStatus` plugin collects the V8 engine's optimization
status for a given function after it has been benchmarked.
================================================
FILE: examples/.gitignore
================================================
================================================
FILE: examples/benchmark-comparison/README.md
================================================
# Benchmark Comparison Framework
This directory contains a framework for comparing `bench-node` against other popular benchmark libraries:
- [Benchmark.js](https://benchmarkjs.com/)
- [Mitata](https://github.com/evanwashere/mitata)
- [Tinybench](https://github.com/tinylibs/tinybench)
## Test Cases
The comparison focuses on four key test scenarios:
1. **JIT Optimizable** - Simple operations that the JavaScript JIT compiler might optimize away
2. **Regex Test** - Regular expression pattern matching operations
3. **Fibonacci** - CPU-bound recursive calculation
## Running the Comparisons
To run any of the comparison scripts, use:
```bash
node --allow-natives-syntax examples/benchmark-comparison/comparison.js
```
> **Note:** All bench-node scripts must be run with the `--allow-natives-syntax` flag.
================================================
FILE: examples/benchmark-comparison/comparison.js
================================================
const { Suite } = require('../../lib');
const Benchmark = require('benchmark');
const { bench, run: mitataRun } = require('mitata');
const { Bench } = require('tinybench');
const testSuites = {
'simple-operations': {
name: 'Simple Operations',
tests: {
'jit-optimizable': {
name: 'JIT Optimizable',
fn: function() {
const x = 1;
const y = 2;
return x + y;
}
}
}
},
'string-processing': {
name: 'String Processing',
tests: {
'regex-test': {
name: 'Regex Test',
fn: function() {
const pattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
const emails = [
'test@example.com',
'invalid-email',
'another.test@example.co.uk',
'john.doe123@sub.domain.com'
];
return emails.map(email => pattern.test(email));
}
}
}
},
'cpu-intensive': {
name: 'CPU Intensive',
tests: {
'fibonacci-10': {
name: 'Fibonacci(10)',
fn: function() {
const n = 10;
let a = 0, b = 1;
for (let i = 2; i <= n; i++) {
[a, b] = [b, a + b];
}
return b;
}
},
'fibonacci-30': {
name: 'Fibonacci(30)',
fn: function() {
const n = 30;
let a = 0, b = 1;
for (let i = 2; i <= n; i++) {
[a, b] = [b, a + b];
}
return b;
}
},
'fibonacci-40': {
name: 'Fibonacci(40)',
fn: function() {
const n = 40;
let a = 0, b = 1;
for (let i = 2; i <= n; i++) {
[a, b] = [b, a + b];
}
return b;
}
},
'fibonacci-recursive-10': {
name: 'Fibonacci Recursive(10)',
fn: function() {
function fibRecursive(n) {
if (n <= 1) return n;
return fibRecursive(n - 1) + fibRecursive(n - 2);
}
return fibRecursive(10);
}
}
}
}
};
function formatNumber(num) {
return new Intl.NumberFormat().format(Math.round(num));
}
async function runBenchNode() {
console.log('\n🔍 Running bench-node tests... (detailed output hidden)');
// Save original console.log
const originalConsoleLog = console.log;
// Replace with no-op function
console.log = () => {};
const results = [];
try {
for (const [suiteKey, suite] of Object.entries(testSuites)) {
const benchSuite = new Suite({ name: suite.name });
Object.entries(suite.tests).forEach(([testKey, test]) => {
benchSuite.add(test.name, test.fn);
});
try {
const suiteResults = await benchSuite.run();
// Collect results
suiteResults.forEach(result => {
// Extract ops/sec from the bench-node result object
// The opsSec field contains the operations per second value
let opsPerSec = 0;
if (typeof result.opsSec === 'number') {
// Direct extraction from the opsSec field
opsPerSec = result.opsSec;
} else if (typeof result.hz === 'number') {
// Fallback to hz if available
opsPerSec = result.hz;
} else if (result.stats && typeof result.stats.mean === 'number') {
// Mean is in nanoseconds, convert to ops/sec
opsPerSec = 1_000_000_000 / result.stats.mean;
} else if (typeof result.avg === 'number') {
// Avg is in seconds, convert to ops/sec
opsPerSec = 1 / result.avg;
}
results.push({
tool: 'bench-node',
suite: suite.name,
test: result.name,
opsPerSec: opsPerSec,
margin: result.rme || 0
});
});
} catch (error) {
// Temporarily restore console.log for error reporting
const tempConsole = console.log;
console.log = originalConsoleLog;
console.log(`Error running benchmark suite ${suite.name}: ${error.message}`);
console.log = tempConsole;
// Add placeholder results for this suite so we don't lose the comparison
Object.entries(suite.tests).forEach(([testKey, test]) => {
results.push({
tool: 'bench-node',
suite: suite.name,
test: test.name,
opsPerSec: 0,
margin: 0
});
});
}
}
} catch (error) {
// Temporarily restore console.log for error reporting
const tempConsole = console.log;
console.log = originalConsoleLog;
console.log(`Error in bench-node benchmarks: ${error.message}`);
console.log = tempConsole;
}
// Restore original console.log
console.log = originalConsoleLog;
return results;
}
async function runBenchmarkJS() {
console.log('\n🔍 Running benchmark.js tests... (detailed output hidden)');
// Save original console.log
const originalConsoleLog = console.log;
// Replace with no-op function
console.log = () => {};
const results = [];
try {
for (const [suiteKey, suite] of Object.entries(testSuites)) {
await new Promise((resolve) => {
const benchSuite = new Benchmark.Suite(suite.name);
Object.entries(suite.tests).forEach(([testKey, test]) => {
benchSuite.add(test.name, test.fn);
});
benchSuite
.on('cycle', (event) => {
// Collect result for each test
results.push({
tool: 'benchmark.js',
suite: suite.name,
test: event.target.name,
opsPerSec: event.target.hz,
margin: event.target.stats.rme
});
})
.on('complete', function() {
resolve();
})
.run();
});
}
} catch (error) {
// Temporarily restore console.log for error reporting
const tempConsole = console.log;
console.log = originalConsoleLog;
console.log(`Error in benchmark.js benchmarks: ${error.message}`);
console.log = tempConsole;
}
// Restore original console.log
console.log = originalConsoleLog;
return results;
}
async function runMitata() {
console.log('\n🔍 Running mitata tests... (detailed output hidden)');
// Save original console.log
const originalConsoleLog = console.log;
// Replace with no-op function
console.log = () => {};
// Create a results collection
const results = [];
try {
// Store test to name mappings for result collection
const testMap = new Map();
for (const [suiteKey, suite] of Object.entries(testSuites)) {
// Estimate performance based on test function execution time
Object.entries(suite.tests).forEach(([testKey, test]) => {
const fullName = `${suite.name}: ${test.name}`;
bench(fullName, test.fn);
// Create a test mapping
testMap.set(fullName, { suite: suite.name, test: test.name });
// Do a simple performance estimate (not as accurate as real benchmark)
// This is a fallback since we can't easily extract mitata results
const start = process.hrtime.bigint();
const iterations = 1000;
for (let i = 0; i < iterations; i++) {
test.fn();
}
const end = process.hrtime.bigint();
const avgNs = Number(end - start) / iterations;
const estOpsPerSec = 1_000_000_000 / avgNs;
// Add estimated result
results.push({
tool: 'mitata',
suite: suite.name,
test: test.name,
opsPerSec: estOpsPerSec,
margin: 5 // Rough estimate of margin
});
});
}
// Run mitata benchmarks silently
await mitataRun();
} catch (error) {
// Temporarily restore console.log for error reporting
const tempConsole = console.log;
console.log = originalConsoleLog;
console.log(`Error in mitata benchmarks: ${error.message}`);
console.log = tempConsole;
}
// Restore original console.log
console.log = originalConsoleLog;
return results;
}
async function runTinyBench() {
console.log('\n🔍 Running tinybench tests... (detailed output hidden)');
// Save original console.log
const originalConsoleLog = console.log;
// Replace with no-op function
console.log = () => {};
const allResults = [];
try {
// Create benchmarks for each suite
for (const [suiteKey, suite] of Object.entries(testSuites)) {
const benchmark = new Bench();
// Add tests to suite
Object.entries(suite.tests).forEach(([testKey, test]) => {
benchmark.add(test.name, test.fn);
});
// Run benchmarks
await benchmark.run();
// Format and collect results
const suiteResults = benchmark.tasks.map(({ name, result }) => ({
name,
ops: result?.hz || 0,
margin: result?.rme ? `±${result?.rme.toFixed(2)}%` : 'N/A'
}));
suiteResults.forEach(result => {
// Extract numeric margin value from the margin string (e.g., '±1.20%' -> 1.20)
let marginValue = 0;
if (result.margin && result.margin !== 'N/A') {
const match = result.margin.match(/[0-9.]+/);
if (match) {
marginValue = parseFloat(match[0]);
}
}
allResults.push({
tool: 'tinybench',
suite: suite.name,
test: result.name,
opsPerSec: result.ops,
margin: marginValue
});
});
}
} catch (error) {
// Temporarily restore console.log for error reporting
const tempConsole = console.log;
console.log = originalConsoleLog;
console.log(`Error in tinybench benchmarks: ${error.message}`);
console.log = tempConsole;
}
// Restore original console.log
console.log = originalConsoleLog;
return allResults;
}
function compareResults(results) {
console.log('\n📈 BENCHMARK COMPARISON SUMMARY');
console.log('============================');
// Group results by suite and test
const resultsBySuiteAndTest = {};
results.forEach(result => {
// Ensure we have valid ops/sec values for comparison
if (isNaN(result.opsPerSec) || result.opsPerSec === undefined || result.opsPerSec === null) {
result.opsPerSec = 0;
}
const key = `${result.suite} - ${result.test}`;
if (!resultsBySuiteAndTest[key]) {
resultsBySuiteAndTest[key] = [];
}
resultsBySuiteAndTest[key].push(result);
});
// For each test case, compare across tools
Object.entries(resultsBySuiteAndTest).forEach(([testCase, testResults]) => {
console.log(`\n📏 Test Case: ${testCase}`);
const comparisonTable = testResults.map(result => ({
'Tool': result.tool,
'ops/sec': formatNumber(result.opsPerSec || 0),
'margin': result.margin ? `±${result.margin.toFixed(2)}%` : 'N/A',
'relative': '1x'
}));
// Find the fastest tool for this test (with valid non-zero result)
const validResults = testResults.filter(r => r.opsPerSec > 0);
const fastest = validResults.length > 0 ?
validResults.reduce((max, result) => result.opsPerSec > max.opsPerSec ? result : max, validResults[0]) :
{ opsPerSec: 1 };
// Calculate relative performance
comparisonTable.forEach(row => {
const result = testResults.find(r => r.tool === row['Tool']);
if (result && result.opsPerSec > 0 && fastest.opsPerSec > 0) {
const relative = result.opsPerSec / fastest.opsPerSec;
row['relative'] = `${relative.toFixed(2)}x`;
} else {
row['relative'] = 'N/A';
}
});
// Sort by performance (descending)
comparisonTable.sort((a, b) => {
const aResult = testResults.find(r => r.tool === a['Tool']);
const bResult = testResults.find(r => r.tool === b['Tool']);
const aSpeed = aResult && aResult.opsPerSec > 0 ? aResult.opsPerSec : 0;
const bSpeed = bResult && bResult.opsPerSec > 0 ? bResult.opsPerSec : 0;
return bSpeed - aSpeed;
});
console.table(comparisonTable);
});
}
async function main() {
console.log('🚀 BENCHMARK COMPARISON');
console.log('======================');
console.log('Comparing: bench-node, benchmark.js, mitata, tinybench\n');
try {
const results = [];
// Run all benchmarks and collect results
const benchNodeResults = await runBenchNode();
const benchmarkJsResults = await runBenchmarkJS();
const mitataResults = await runMitata();
const tinyBenchResults = await runTinyBench();
// Combine all results
results.push(...benchNodeResults);
results.push(...benchmarkJsResults);
results.push(...mitataResults);
results.push(...tinyBenchResults);
// Compare results across all tools
compareResults(results);
console.log('\n✅ All benchmarks completed successfully!');
} catch (error) {
console.error('❌ Error running benchmarks:', error);
}
}
main();
================================================
FILE: examples/chart-report/node.js
================================================
const { Suite, chartReport } = require('../../lib');
const assert = require('node:assert');
async function runSuiteOne() {
const suite = new Suite({
reporter: chartReport,
});
await suite
.add('test 1', function () {
const pattern = /[123]/g
})
.add('test 2', function () {
const subject = '123123123123123123123123123123123123123123123123'
const r = subject.replace(/1/g, 'a').replace(/2/g, 'b').replace(/3/g, 'c')
})
.run();
}
async function runSuiteTwo() {
const suite = new Suite({
reporter: chartReport,
reporterOptions: {
printHeader: false
}
});
await suite
.add('test 3', function () {
const pattern = /[123]/g
})
.add('test 4', function () {
const subject = '123123123123123123123123123123123123123123123123'
const r = subject.replace(/1/g, 'a').replace(/2/g, 'b').replace(/3/g, 'c')
assert.ok(r)
})
.run();
}
async function main() {
await runSuiteOne()
process.stdout.write('\n\n')
await runSuiteTwo()
}
main()
================================================
FILE: examples/create-uint32array/node.js
================================================
const { Suite } = require('../../lib');
const suite = new Suite();
suite
.add(`new Uint32Array(1024)`, { baseline: true }, function () {
return new Uint32Array(1024);
})
.add(`new Uint32Array(1024) with 10 repetitions`, {repeatSuite: 10}, function () {
return new Uint32Array(1024);
})
.add(`new Uint32Array(1024) with min samples of 50`, {minSamples: 50}, function() {
return new Uint32Array(1024);
})
.add(`[Managed] new Uint32Array(1024)`, function (timer) {
const assert = require('node:assert');
let r;
timer.start();
for (let i = 0; i < timer.count; i++) {
r = new Uint32Array(1024);
}
timer.end(timer.count);
assert.ok(r);
})
.run();
================================================
FILE: examples/create-uint32array/node.js.log
================================================
new Uint32Array(1024) x 2,930,763 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(332.08ns ... 355.52ns) p75=344.36ns p99=355.52ns
[Managed] new Uint32Array(1024) x 2,934,651 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(335.94ns ... 357.76ns) p75=340.16ns p99=357.76ns
----------------------------------------------------------------------------
new Uint32Array(1024) x 2,967,727 ops/sec (9 runs sampled) v8-never-optimize=true min..max=(330.75ns ... 337.55ns) p75=334.98ns p99=337.55ns
[Managed] new Uint32Array(1024) x 3,029,150 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(326.51ns ... 341.28ns) p75=331.01ns p99=341.28ns
----------------------------------------------------------------------------
new Uint32Array(1024) x 2,920,851 ops/sec (12 runs sampled) v8-never-optimize=true min..max=(332.88ns ... 363.08ns) p75=348.19ns p99=363.08ns
[Managed] new Uint32Array(1024) x 3,032,256 ops/sec (12 runs sampled) v8-never-optimize=true min..max=(327.13ns ... 343.75ns) p75=329.19ns p99=343.75ns
----------------------------------------------------------------------------
================================================
FILE: examples/crypto-verify/node.js
================================================
const crypto = require('node:crypto');
const { readFileSync } = require('fs');
const { Suite } = require('../../lib');
const rsaPrivateKey = readFileSync(`${__dirname}/private-key.pem`, 'utf-8');
const rsaPublicKey = readFileSync(`${__dirname}/public-key.pem`, 'utf-8');
const thing = 'hello world';
const algorithm = 'RSA-SHA256';
const signature = crypto.createSign(algorithm).update(thing).sign(rsaPrivateKey, 'base64');
const suite = new Suite();
suite
.add(`crypto.createVerify('${algorithm}')`, function () {
var verifier = crypto.createVerify(algorithm);
verifier.update(thing);
verifier.verify(rsaPublicKey, signature, 'base64');
})
.add(`crypto.verify('${algorithm}')`, function () {
crypto.verify(algorithm, thing, rsaPublicKey, Buffer.from(signature, 'base64'));
})
.add(`[Managed] crypto.createVerify('RSA-SHA256')`, function (timer) {
const crypto = require('node:crypto');
const { readFileSync } =require('node:fs');
const assert = require('node:assert');
const rsaPrivateKey = readFileSync(`${__dirname}/private-key.pem`, 'utf-8');
const rsaPublicKey = readFileSync(`${__dirname}/public-key.pem`, 'utf-8');
const thing = 'hello world'
const signature = crypto.createSign('RSA-SHA256').update(thing).sign(rsaPrivateKey, 'base64')
let verifier;
timer.start();
for (let i = 0; i < timer.count; i++) {
verifier = crypto.createVerify('RSA-SHA256')
verifier.update(thing)
verifier.verify(rsaPublicKey, signature, 'base64')
}
timer.end(timer.count);
assert.ok(verifier);
})
.add(`[Managed] crypto.verify('RSA-SHA256')`, function (timer) {
const crypto = require('node:crypto');
const { readFileSync } = require('node:fs');
const assert = require('node:assert');
const rsaPrivateKey = readFileSync(`${__dirname}/private-key.pem`, 'utf-8');
const rsaPublicKey = readFileSync(`${__dirname}/public-key.pem`, 'utf-8');
const thing = 'hello world'
const signature = crypto.createSign('RSA-SHA256').update(thing).sign(rsaPrivateKey, 'base64')
let r;
timer.start();
for (let i = 0; i < timer.count; i++) {
r = crypto.verify('RSA-SHA256', thing, rsaPublicKey, Buffer.from(signature, 'base64'));
}
timer.end(timer.count);
assert.ok(r);
})
.run();
================================================
FILE: examples/crypto-verify/node.js.log
================================================
crypto.createVerify('RSA-SHA256') x 10,925 ops/sec (12 runs sampled) v8-never-optimize=true min..max=(88.50us ... 93.51us) p75=91.54us p99=93.51us
crypto.verify('RSA-SHA256') x 10,988 ops/sec (9 runs sampled) v8-never-optimize=true min..max=(90.93us ... 91.84us) p75=91.58us p99=91.84us
[Managed] crypto.createVerify('RSA-SHA256') x 10,520 ops/sec (9 runs sampled) v8-never-optimize=true min..max=(88.44us ... 92.23us) p75=91.47us p99=92.23us
[Managed] crypto.verify('RSA-SHA256') x 10,948 ops/sec (9 runs sampled) v8-never-optimize=true min..max=(89.98us ... 92.36us) p75=91.49us p99=92.36us
----------------------------------------------------------------------------
crypto.createVerify('RSA-SHA256') x 10,807 ops/sec (8 runs sampled) v8-never-optimize=true min..max=(91.66us ... 92.58us) p75=92.23us p99=92.58us
crypto.verify('RSA-SHA256') x 10,964 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(88.95us ... 92.46us) p75=91.83us p99=92.46us
[Managed] crypto.createVerify('RSA-SHA256') x 10,954 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(90.08us ... 92.88us) p75=91.72us p99=92.88us
[Managed] crypto.verify('RSA-SHA256') x 10,990 ops/sec (9 runs sampled) v8-never-optimize=true min..max=(90.31us ... 92.43us) p75=91.43us p99=92.43us
----------------------------------------------------------------------------
crypto.createVerify('RSA-SHA256') x 10,890 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(87.88us ... 94.08us) p75=92.96us p99=94.08us
crypto.verify('RSA-SHA256') x 10,906 ops/sec (9 runs sampled) v8-never-optimize=true min..max=(91.03us ... 92.45us) p75=91.66us p99=92.45us
[Managed] crypto.createVerify('RSA-SHA256') x 11,050 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(87.94us ... 92.09us) p75=91.88us p99=92.09us
[Managed] crypto.verify('RSA-SHA256') x 10,990 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(90.29us ... 92.13us) p75=91.92us p99=92.13us
----------------------------------------------------------------------------
================================================
FILE: examples/crypto-verify/private-key.pem
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAz7QweYwr7puNtc8/6jf4pADXfY2/HVq7LgI7JXkAwW5HTEjU
Rvu02SYC7n/ylr86Da4hcAEAFBC2baVh7J0EnLo6D4FA3yV4s5zj7tqL+p2CdniY
MDEny+uxS/9eRuLf1RIYHjSVB6wOoU4lg9FtDOQA8W09ZDLvc3/4ZSwEf9O2J5Vm
qcSGaPt35eIEBS0iIMiOUCGqbUcVMZkp4JO7I65HOK/g0Scvb1LLY6f4qDsThqyi
ID/NXM5LtjnztRP+9dCLT9DdwC+LeU2te6vcEwC2bPYs3nUwkZL4qqIWO6GHaavn
gFKSmbmc9muVAPmYW/ffMtznKZaWCp9Li0JGuQIDAQABAoIBAQC7OI7hYSpQgEKy
eUgBlcY3/tI/SD/W8+v5QuWRl4rI0ODPsG44NbcEbbECzq4al/B6WFWnoh8x9waZ
uxOTts1rgKnJRBb3jc1JCcijirfWhZgNthJojkZzF9bOzDds6iAc7Zxzza3wJnVh
jRFfyqzji7oV5QQLh6YzlEyQ1aaQmOKTkCDV1UDetI8iItUvq3i8EwsFmtfvXjTl
aymL/xSd3R6w3WDEd8PS5ZPDoFmkt2h/4IhOo6bVXLwWBWqcopRAvzGQrc806c2l
jjePwk9fddIG+vIHc8DHqaC/WzMR6iBt3K7Q9Eyu4k97qeOxB5TIEVlhxg2DJON9
DlGtInXpAoGBAPje3L0xL441i7kh7GqiWtXiDgxVwdPmN5p7n755MddSxvu8894h
T9/LxwAVcpolXANgyRtrl5bu/gXgznKefmVr976BzG2DY33PESf5FnHZAmixWSfz
VOdfVlz+Bk/BI+nVwmeT0+8NSHJ6TJ/9NOSzI9ctUceszIHG5/Gfc2wHAoGBANWn
bHNT7aphFqyUMfWyc6+h8Radq/RVMrOGeD2wRRqUTeb2Ydbx2OwB+gcwnlbsQjfS
BYAa3kn1z5K5kOigB9qu6x7zuM9SMReKIlMg3NpYVyKKj+JaNF3YArFa5TWy0B26
wNJ0FiiZyPq4eP5hPwM+Bed4ytIRWa54P5GeRYc/AoGAWT8ijb4rvaW6G4Ps0jiy
tmzAeO/v+FtgqUeX+6helUccEH6sPYZYrHrZPFB0ro6jNprow6qLzBacheMeZcAs
t5ZGW80UUFmDvkQZdOpAgEdAM+cVf9wlIGvx/psiDEvI4zxC4P4ETH/I8TSmceFN
rI4JVkrsPtza4ddAqkdyDtUCgYAaoMs7dHJikccpqy6u2JbihORvVSdhRF0VUuUZ
iyaRsXokFwEKsQnAIF7xFnYljzyRiHN3C+I4hZJhTw9obsmLz9EuAmI+NJg5vtWY
Vrgv3mK9w1c7dtKf/5QWVqXKk4asreHqWN2KIeCSnvs1eRlJZimGN9/PXqo2vHXv
yDISMQKBgE22EhVB99rEsLXDCXOUHIFGpW46ZavQc4DLXg+uIQm3y0WWyc0oyNF8
9kr1luu5QI5PV9mWpSKa3SKmc74biyxhlUp/DYuMyuSXpdDegMAKheAHVXP9ToU/
5qhq4HBcISN3CwSAYcFcbqC3cB07T+IwX6NTEz4zel4gty8t/tsM
-----END RSA PRIVATE KEY-----
================================================
FILE: examples/crypto-verify/public-key.pem
================================================
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz7QweYwr7puNtc8/6jf4
pADXfY2/HVq7LgI7JXkAwW5HTEjURvu02SYC7n/ylr86Da4hcAEAFBC2baVh7J0E
nLo6D4FA3yV4s5zj7tqL+p2CdniYMDEny+uxS/9eRuLf1RIYHjSVB6wOoU4lg9Ft
DOQA8W09ZDLvc3/4ZSwEf9O2J5VmqcSGaPt35eIEBS0iIMiOUCGqbUcVMZkp4JO7
I65HOK/g0Scvb1LLY6f4qDsThqyiID/NXM5LtjnztRP+9dCLT9DdwC+LeU2te6vc
EwC2bPYs3nUwkZL4qqIWO6GHaavngFKSmbmc9muVAPmYW/ffMtznKZaWCp9Li0JG
uQIDAQAB
-----END PUBLIC KEY-----
================================================
FILE: examples/csv-report/node.js
================================================
const { Suite } = require('../../lib');
const assert = require('node:assert');
const suite = new Suite();
suite
.add('single with matcher', function () {
const pattern = /[123]/g
const replacements = { 1: 'a', 2: 'b', 3: 'c' }
const subject = '123123123123123123123123123123123123123123123123'
const r = subject.replace(pattern, m => replacements[m])
assert.ok(r);
})
.add('multiple replaces', function () {
const subject = '123123123123123123123123123123123123123123123123'
const r = subject.replace(/1/g, 'a').replace(/2/g, 'b').replace(/3/g, 'c')
assert.ok(r);
})
.run();
================================================
FILE: examples/dce-detection/example.js
================================================
const { Suite } = require("../../lib/index");
// Enable DCE detection to catch benchmarks that may be optimized away
const suite = new Suite({
detectDeadCodeElimination: true,
});
// Example 1: Likely to trigger DCE warning - result not used
suite.add("simple addition (likely DCE)", () => {
const result = 1 + 1;
// result is never used - JIT might optimize this away
});
// Example 2: Result is used - should not trigger warning
suite.add("simple addition (used)", () => {
const result = 1 + 1;
if (result !== 2) throw new Error("Unexpected result");
});
// Example 3: Array creation without use - likely DCE
suite.add("array creation (likely DCE)", () => {
const arr = new Array(100);
// arr is never accessed - might be optimized away
});
// Example 4: Array creation with access - should not trigger warning
suite.add("array creation (accessed)", () => {
const arr = new Array(100);
arr[0] = 1; // Using the array
});
// Example 5: Object creation without use - likely DCE
suite.add("object creation (likely DCE)", () => {
const obj = { x: 1, y: 2, z: 3 };
// obj is never accessed
});
// Example 6: More realistic computation
suite.add("string operations", () => {
const str1 = "hello";
const str2 = "world";
const result = str1 + " " + str2;
if (!result.includes("hello")) throw new Error("Missing hello");
});
suite.run();
================================================
FILE: examples/dce-detection/with-dce-disabled.js
================================================
const { Suite } = require("../../lib/index");
// Default behavior - DCE detection is disabled, V8NeverOptimizePlugin is used
// You don't need to set detectDeadCodeElimination: false explicitly
const suite = new Suite();
// These benchmarks will run with V8NeverOptimizePlugin, so they'll be slower
// but more deterministic and won't be optimized away
suite.add("simple addition", () => {
const result = 1 + 1;
});
suite.add("array creation", () => {
const arr = new Array(100);
});
suite.run();
================================================
FILE: examples/dce-detection/without-never-optimize.js
================================================
const { Suite } = require("../../lib/index");
// Enable DCE detection - this automatically disables V8NeverOptimizePlugin
// In this mode, V8 optimizations occur naturally and DCE warnings help identify issues
const suite = new Suite({
detectDeadCodeElimination: true,
});
// Example 1: Likely to trigger DCE warning - result not used
suite.add("simple addition (likely DCE)", () => {
const result = 1 + 1;
// result is never used - JIT will optimize this away
});
// Example 2: Result is used - should not trigger warning
suite.add("simple addition (used)", () => {
const result = 1 + 1;
if (result !== 2) throw new Error("Unexpected result");
});
// Example 3: Array creation without use - likely DCE
suite.add("array creation (likely DCE)", () => {
const arr = new Array(100);
// arr is never accessed - will be optimized away
});
// Example 4: Array creation with access - should not trigger warning
suite.add("array creation (accessed)", () => {
const arr = new Array(100);
arr[0] = 1; // Using the array
});
// Example 5: More realistic computation that takes time
suite.add("actual work", () => {
let sum = 0;
for (let i = 0; i < 100; i++) {
sum += Math.sqrt(i);
}
if (sum < 0) throw new Error("Impossible");
});
suite.run();
================================================
FILE: examples/deleting-properties/node.js
================================================
const { Suite } = require('../../lib');
const suite = new Suite();
const NullObject = function NullObject() { }
NullObject.prototype = Object.create(null);
%NeverOptimizeFunction(NullObject);
suite
.add('Using delete property', function () {
const data = { x: 1, y: 2, z: 3 }
delete data.y
data.x
data.y
data.z
})
.add('Using delete property (proto: null)', function () {
const data = { __proto__: null, x: 1, y: 2, z: 3 }
delete data.y
data.x
data.y
data.z
})
.add('Using delete property (cached proto: null)', function () {
const data = new NullObject()
data.x = 1
data.y = 2
data.z = 3
delete data.y
data.x
data.y
data.z
})
.add('Using undefined assignment', function () {
const data = { x: 1, y: 2, z: 3 }
data.y = undefined
data.x
data.y
data.z
})
.add('Using undefined assignment (proto: null)', function () {
const data = { __proto__: null, x: 1, y: 2, z: 3 }
data.y = undefined
data.x
data.y
data.z
})
.add('Using undefined property (cached proto: null)', function () {
const data = new NullObject()
data.x = 1
data.y = 2
data.z = 3
data.y = undefined
data.x
data.y
data.z
})
.add('[Managed] Using undefined property (cached proto: null)', function (t) {
const NullObject = function () { }
NullObject.prototype = Object.create(null)
t.start();
for (let i = 0; i < t.count; i++) {
const data = new NullObject()
data.x = 1
data.y = 2
data.z = 3
data.y = undefined
data.x
data.y
data.z
}
t.end(t.count);
})
.run();
================================================
FILE: examples/deleting-properties/node.js.log
================================================
Using delete property x 7,763,866 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(128.11ns ... 129.99ns) p75=129.03ns p99=129.99ns
Using delete property (proto: null) x 22,946,068 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(43.13ns ... 44.11ns) p75=43.74ns p99=44.11ns
Using delete property (cached proto: null) x 7,331,951 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(135.64ns ... 137.39ns) p75=136.52ns p99=137.39ns
Using undefined assignment x 133,449,849 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(7.45ns ... 7.56ns) p75=7.53ns p99=7.56ns
Using undefined assignment (proto: null) x 25,432,632 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(38.72ns ... 40.06ns) p75=39.60ns p99=40.06ns
Using undefined property (cached proto: null) x 58,871,059 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(16.92ns ... 17.12ns) p75=17.01ns p99=17.12ns
[Managed] Using undefined property (cached proto: null) x 35,463,712 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(28.05ns ... 28.43ns) p75=28.25ns p99=28.43ns
----------------------------------------------------------------------------
Using delete property x 6,669,856 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(149.14ns ... 151.02ns) p75=150.12ns p99=151.02ns
Using delete property (proto: null) x 15,242,131 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(64.77ns ... 66.38ns) p75=66.25ns p99=66.38ns
Using delete property (cached proto: null) x 5,920,843 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(167.87ns ... 170.33ns) p75=169.07ns p99=170.33ns
Using undefined assignment x 78,647,245 ops/sec (12 runs sampled) v8-never-optimize=true min..max=(12.62ns ... 12.83ns) p75=12.70ns p99=12.83ns
Using undefined assignment (proto: null) x 16,278,910 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(60.79ns ... 62.25ns) p75=61.57ns p99=62.25ns
Using undefined property (cached proto: null) x 27,262,884 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(36.54ns ... 36.65ns) p75=36.62ns p99=36.65ns
[Managed] Using undefined property (cached proto: null) x 28,068,785 ops/sec (12 runs sampled) v8-never-optimize=true min..max=(35.07ns ... 35.79ns) p75=35.67ns p99=35.79ns
----------------------------------------------------------------------------
Using delete property x 7,632,095 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(129.63ns ... 132.85ns) p75=131.19ns p99=132.85ns
Using delete property (proto: null) x 23,040,357 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(43.06ns ... 44.20ns) p75=43.56ns p99=44.20ns
Using delete property (cached proto: null) x 7,222,103 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(137.05ns ... 139.88ns) p75=139.18ns p99=139.88ns
Using undefined assignment x 133,133,677 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(7.49ns ... 7.53ns) p75=7.52ns p99=7.53ns
Using undefined assignment (proto: null) x 25,217,884 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(39.24ns ... 40.48ns) p75=39.89ns p99=40.48ns
Using undefined property (cached proto: null) x 58,899,972 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(16.94ns ... 17.03ns) p75=16.99ns p99=17.03ns
[Managed] Using undefined property (cached proto: null) x 34,904,120 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(28.34ns ... 29.89ns) p75=28.84ns p99=29.89ns
----------------------------------------------------------------------------
================================================
FILE: examples/empty/node.js
================================================
const { Suite } = require('../../lib');
const suite = new Suite();
suite
.add(`empty`, function () {})
.add(`empty async`, async function () {})
.run();
================================================
FILE: examples/empty/node.js.log
================================================
empty x 218,984,985 ops/sec (12 runs sampled) v8-never-optimize=true min..max=(4.53ns ... 4.66ns) p75=4.58ns p99=4.66ns
empty async x 20,898,403 ops/sec (9 runs sampled) v8-never-optimize=true min..max=(47.46ns ... 48.57ns) p75=48.04ns p99=48.57ns
----------------------------------------------------------------------------
empty x 220,571,877 ops/sec (12 runs sampled) v8-never-optimize=true min..max=(4.49ns ... 4.61ns) p75=4.58ns p99=4.61ns
empty async x 20,925,022 ops/sec (14 runs sampled) v8-never-optimize=true min..max=(37.43ns ... 48.60ns) p75=48.03ns p99=48.60ns
----------------------------------------------------------------------------
empty x 218,923,690 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(4.52ns ... 4.64ns) p75=4.59ns p99=4.64ns
empty async x 20,792,931 ops/sec (12 runs sampled) v8-never-optimize=true min..max=(42.94ns ... 49.45ns) p75=48.54ns p99=49.45ns
----------------------------------------------------------------------------
================================================
FILE: examples/fs-read-async/node.js
================================================
const { Suite } = require('../../lib');
const { readFile } = require('node:fs/promises');
const { resolve } = require('node:path');
const suite = new Suite();
const sampleFile = resolve(__dirname, 'sample-file.txt');
suite
.add('readFile', async function () {
const r = await readFile(sampleFile);
})
.add('readFile utf-8', async function () {
const r = await readFile(sampleFile, 'utf-8');
})
.add('[managed] readFile', async function (timer) {
const { readFile } = require('node:fs/promises');
const { resolve } = require('node:path');
const assert = require('node:assert');
const sampleFile = resolve(__dirname, 'sample-file.txt');
let r;
timer.start();
for (let i = 0; i < timer.count; i++) {
r = await readFile(sampleFile);
}
timer.end(timer.count);
assert.ok(r);
})
.add('[managed] readFile utf-8', async function (timer) {
const { readFile } = require('node:fs/promises');
const { resolve } = require('node:path');
const assert = require('node:assert');
const sampleFile = resolve(__dirname, 'sample-file.txt');
let r;
timer.start();
for (let i = 0; i < timer.count; i++) {
r = await readFile(sampleFile, 'utf-8');
}
timer.end(timer.count);
assert.ok(r);
})
.run();
================================================
FILE: examples/fs-read-async/node.js.log
================================================
readFile x 23,167 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(42.59us ... 43.59us) p75=43.42us p99=43.59us
readFile utf-8 x 23,021 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(43.14us ... 43.92us) p75=43.64us p99=43.92us
[managed] readFile x 23,386 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(42.63us ... 43.04us) p75=42.89us p99=43.04us
[managed] readFile utf-8 x 23,302 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(42.76us ... 43.30us) p75=43.01us p99=43.30us
----------------------------------------------------------------------------
readFile x 23,177 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(42.70us ... 43.54us) p75=43.31us p99=43.54us
readFile utf-8 x 23,155 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(42.34us ... 43.53us) p75=43.15us p99=43.53us
[managed] readFile x 23,538 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(42.22us ... 42.82us) p75=42.59us p99=42.82us
[managed] readFile utf-8 x 23,458 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(42.11us ... 43.20us) p75=42.86us p99=43.20us
----------------------------------------------------------------------------
readFile x 23,235 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(42.73us ... 43.41us) p75=43.17us p99=43.41us
readFile utf-8 x 23,130 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(42.90us ... 44.40us) p75=43.62us p99=44.40us
[managed] readFile x 23,490 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(42.40us ... 42.89us) p75=42.66us p99=42.89us
[managed] readFile utf-8 x 23,553 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(41.99us ... 42.81us) p75=42.58us p99=42.81us
----------------------------------------------------------------------------
================================================
FILE: examples/fs-read-async/node.managed.js
================================================
const { Suite } = require('../../lib');
const suite = new Suite();
suite
.add('readFile', async function (timer) {
const { readFile } = require('node:fs/promises');
const { resolve } = require('node:path');
const assert = require('node:assert');
const sampleFile = resolve(__dirname, 'sample-file.txt');
let r;
timer.start();
for (let i = 0; i < timer.count; i++) {
r = await readFile(sampleFile);
}
timer.end(timer.count);
assert.ok(r);
})
.add('readFile utf-8', async function (timer) {
const { readFile } = require('node:fs/promises');
const { resolve } = require('node:path');
const assert = require('node:assert');
const sampleFile = resolve(__dirname, 'sample-file.txt');
let r;
timer.start();
for (let i = 0; i < timer.count; i++) {
r = await readFile(sampleFile, 'utf-8');
}
timer.end(timer.count);
assert.ok(r);
})
.run();
================================================
FILE: examples/fs-read-async/node.managed.js.log
================================================
readFile x 23,066 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(42.59us ... 43.63us) p75=43.20us p99=43.63us
readFile utf-8 x 23,334 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(42.21us ... 43.70us) p75=42.96us p99=43.70us
----------------------------------------------------------------------------
readFile x 23,145 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(42.64us ... 43.18us) p75=43.04us p99=43.18us
readFile utf-8 x 23,324 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(42.54us ... 43.80us) p75=42.93us p99=43.80us
----------------------------------------------------------------------------
readFile x 23,124 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(42.36us ... 43.30us) p75=42.99us p99=43.30us
readFile utf-8 x 23,277 ops/sec (12 runs sampled) v8-never-optimize=true min..max=(41.50us ... 43.88us) p75=42.98us p99=43.88us
----------------------------------------------------------------------------
================================================
FILE: examples/fs-read-async/sample-file.txt
================================================
hello
================================================
FILE: examples/fs-read-sync/node.js
================================================
const { Suite } = require('../../lib');
const { readFileSync } = require('node:fs');
const { resolve } = require('node:path');
const suite = new Suite();
const sampleFile = resolve(__dirname, 'sample-file.txt');
suite
.add('readFileSync', function () {
const r = readFileSync(sampleFile);
})
.add('readFileSync utf-8', function () {
const r = readFileSync(sampleFile, 'utf-8');
})
.add('[Managed] readFileSync', function (timer) {
const { readFileSync } = require('node:fs');
const { resolve } = require('node:path');
const assert = require('node:assert');
const sampleFile = resolve(__dirname, 'sample-file.txt');
let r;
timer.start();
for (let i = 0; i < timer.count; i++) {
r = readFileSync(sampleFile);
}
timer.end(timer.count);
assert.ok(r);
})
.add('[Managed] readFileSync utf-8', function (timer) {
const { readFileSync } = require('node:fs');
const { resolve } = require('node:path');
const assert = require('node:assert');
const sampleFile = resolve(__dirname, 'sample-file.txt');
let r;
timer.start();
for (let i = 0; i < timer.count; i++) {
r = readFileSync(sampleFile, 'utf-8');
}
timer.end(timer.count);
assert.ok(r);
})
.run();
================================================
FILE: examples/fs-read-sync/node.js.log
================================================
readFileSync x 198,828 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(5.01us ... 5.06us) p75=5.04us p99=5.06us
readFileSync utf-8 x 203,828 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(4.88us ... 4.94us) p75=4.92us p99=4.94us
[Managed] readFileSync x 196,632 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(5.05us ... 5.08us) p75=5.07us p99=5.08us
[Managed] readFileSync utf-8 x 203,265 ops/sec (9 runs sampled) v8-never-optimize=true min..max=(4.89us ... 4.92us) p75=4.91us p99=4.92us
----------------------------------------------------------------------------
readFileSync x 198,461 ops/sec (9 runs sampled) v8-never-optimize=true min..max=(5.00us ... 5.05us) p75=5.02us p99=5.05us
readFileSync utf-8 x 205,171 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(4.82us ... 4.86us) p75=4.86us p99=4.86us
[Managed] readFileSync x 193,061 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(5.05us ... 5.48us) p75=5.30us p99=5.48us
[Managed] readFileSync utf-8 x 204,046 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(4.86us ... 4.92us) p75=4.91us p99=4.92us
----------------------------------------------------------------------------
readFileSync x 196,533 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(5.02us ... 5.20us) p75=5.10us p99=5.20us
readFileSync utf-8 x 205,213 ops/sec (10 runs sampled) v8-never-optimize=true min..max=(4.83us ... 4.89us) p75=4.86us p99=4.89us
[Managed] readFileSync x 183,333 ops/sec (12 runs sampled) v8-never-optimize=true min..max=(5.06us ... 6.46us) p75=5.28us p99=6.46us
[Managed] readFileSync utf-8 x 201,593 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(4.90us ... 5.07us) p75=5.06us p99=5.07us
----------------------------------------------------------------------------
================================================
FILE: examples/fs-read-sync/sample-file.txt
================================================
hello
================================================
FILE: examples/html-report/node.js
================================================
const { Suite, htmlReport } = require('../../lib');
const assert = require('node:assert');
const suite = new Suite({
reporter: htmlReport,
});
suite
.add('single with matcher', function () {
const pattern = /[123]/g
const replacements = { 1: 'a', 2: 'b', 3: 'c' }
const subject = '123123123123123123123123123123123123123123123123'
const r = subject.replace(pattern, m => replacements[m])
assert.ok(r);
})
.add('Multiple replaces', function () {
const subject = '123123123123123123123123123123123123123123123123'
const r = subject.replace(/1/g, 'a').replace(/2/g, 'b').replace(/3/g, 'c')
assert.ok(r);
})
.run();
================================================
FILE: examples/html-report/result.html
================================================
Benchmark Visualizer