Skip to content

RafaelGSS/bench-node

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

76 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

bench-node

The bench-node module allows you to measure operations per second of Node.js code blocks.

Install

$ npm install bench-node

Usage

const { Suite } = require('bench-node');

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 property', () => {
  const data = { x: 1, y: 2, z: 3 };
  delete data.y;

  data.x;
  data.y;
  data.z;
});

suite.run().then(results => {
  console.log('Benchmark complete.');
}).catch(err => {
  console.error('Error running benchmarks:', err);
});

This module uses V8 deoptimization to ensure that the code block is not optimized away, producing accurate benchmarks. See the Writing JavaScript Microbenchmark Mistakes section for more details.

$ 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

See the examples folder for more common usage examples.

Table of Contents

  1. Class Suite
    1. suite.add()
    2. suite.run()
  2. Plugins
  3. Using Custom Reporter
  4. Setup and Teardown

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. 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, results are printed to the console.

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

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} 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}

Adds a benchmark function to the suite.

$ 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

suite.run()

  • 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.

Runs all added benchmarks and returns the results.

Plugins

Plugins extend the functionality of the benchmark module.

See Plugins 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): Called when the benchmark completes, allowing plugins to process results.
  • toString(): Returns a string identifier for the plugin.

Example Plugin

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 Custom Reporter

Customize data reporting by providing a reporter function when creating the Suite:

const { Suite } = require('bench-node');

function 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}`);
}

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();
$ 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:

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:

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();

Ensure you call .start() and .end() methods when using the timer argument, or an ERR_BENCHMARK_MISSING_OPERATION error will be thrown.