{"id":15400308,"url":"https://github.com/rafaelgss/bench-node","last_synced_at":"2025-04-05T09:05:15.388Z","repository":{"id":208536162,"uuid":"721878804","full_name":"RafaelGSS/bench-node","owner":"RafaelGSS","description":"A powerful Node.js benchmark library","archived":false,"fork":false,"pushed_at":"2024-10-23T17:59:16.000Z","size":119,"stargazers_count":65,"open_issues_count":2,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-10-25T09:12:38.435Z","etag":null,"topics":["benchmark","benchmarking","nodejs"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RafaelGSS.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-22T00:51:52.000Z","updated_at":"2024-10-24T07:47:49.000Z","dependencies_parsed_at":"2024-01-26T00:23:01.839Z","dependency_job_id":"a358758d-335d-492b-9367-8c16bedacc8e","html_url":"https://github.com/RafaelGSS/bench-node","commit_stats":{"total_commits":93,"total_committers":3,"mean_commits":31.0,"dds":"0.11827956989247312","last_synced_commit":"76d573f1372e72c1b8bd8923d029752dcec9f77a"},"previous_names":["rafaelgss/node-benchmark"],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RafaelGSS%2Fbench-node","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RafaelGSS%2Fbench-node/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RafaelGSS%2Fbench-node/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RafaelGSS%2Fbench-node/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RafaelGSS","download_url":"https://codeload.github.com/RafaelGSS/bench-node/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247217174,"owners_count":20903008,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["benchmark","benchmarking","nodejs"],"created_at":"2024-10-01T15:53:29.824Z","updated_at":"2025-04-05T09:05:15.351Z","avatar_url":"https://github.com/RafaelGSS.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\n  \u003cimg\n    src=\"https://raw.githubusercontent.com/RafaelGSS/bench-node/refs/heads/main/assets/logo.svg\"\n    alt=\"Bench Node logo\"\n  /\u003e\n  Bench Node\n\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#install\"\u003eInstall\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;|\u0026nbsp;\u0026nbsp;\u0026nbsp;\n  \u003ca href=\"#usage\"\u003eUsage\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;|\u0026nbsp;\u0026nbsp;\u0026nbsp;\n  \u003ca href=\"#class-suite\"\u003eSuite\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;|\u0026nbsp;\u0026nbsp;\u0026nbsp;\n  \u003ca href=\"#plugins\"\u003ePlugins\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;|\u0026nbsp;\u0026nbsp;\u0026nbsp;\n  \u003ca href=\"#using-reporter\"\u003eUsing Reporter\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;|\u0026nbsp;\u0026nbsp;\u0026nbsp;\n  \u003ca href=\"#setup-and-teardown\"\u003eSetup and Teardown\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;|\u0026nbsp;\u0026nbsp;\u0026nbsp;\n  \u003ca href=\"#writing-javascript-mistakes\"\u003eWriting JavaScript Mistakes\u003c/a\u003e\n\u003c/p\u003e\n\n[![npm package][npm-img]][npm-url]\n[![Build Status][build-img]][build-url]\n[![Downloads][downloads-img]][downloads-url]\n[![Issues][issues-img]][issues-url]\n\nThe `bench-node` module allows you to measure operations per second of Node.js code blocks.\n\n## Install\n\n```bash\n$ npm install bench-node\n```\n\n## Usage\n\n```cjs\nconst { Suite } = require('bench-node');\n\nconst suite = new Suite();\n\nsuite.add('Using delete property', () =\u003e {\n  const data = { x: 1, y: 2, z: 3 };\n  delete data.y;\n\n  data.x;\n  data.y;\n  data.z;\n});\n\nsuite.run()\n```\n\n```bash\n$ node --allow-natives-syntax my-benchmark.js\nUsing delete property x 3,326,913 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(0ns ... 0ns) p75=0ns p99=0ns\n```\n\nThis module uses V8 deoptimization to help ensure that the code block is not optimized away, producing accurate benchmarks -- but not realistic.\nSee the [Writing JavaScript Microbenchmark Mistakes](#writing-javascript-mistakes) section for more details.\n\nThe [`bench-node-cli`](https://github.com/RafaelGSS/bench-node-cli) tool allows you to execute a `bench-node` benchmark\nfrom any location, eliminating the need to install the `bench-node` package locally.\nSimply use the following command to run your benchmark:\n\n```bash\nnpx bench-node-cli my-benchmark.js\n```\n\nSee the [examples folder](./examples/) for more common usage examples.\n\n## Table of Contents\n\n1. [Class `Suite`](#class-suite)\n    1. [`suite.add()`](#suiteaddname-options-fn)\n    2. [`suite.run()`](#suiterun)\n2. [Plugins](#plugins)\n3. [Using Reporter](#using-reporter)\n    1. [Text Reporter](#textreport-default)\n    2. [Chart Reporter](#chartreport)\n    3. [Custom Reporter](#custom-reporter) \n4. [Setup and Teardown](#setup-and-teardown)\n    1. [Managed Benchmarks](#managed-benchmarks)\n\n## Class: `Suite`\n\n\u003e Stability: 1.1 Active Development\n\nA `Suite` manages and executes benchmark functions. It provides two methods: `add()` and `run()`.\n\n### `new Suite([options])`\n\n* `options` {Object} Configuration options for the suite.\n  * `reporter` {Function} Callback function for reporting results. Receives one argument:\n    * `results` {Object[]} Array of benchmark results:\n      * `name` {string} Benchmark name.\n      * `opsSec` {string} Operations per second.\n      * `iterations` {Number} Number of iterations.\n      * `histogram` {Histogram} Histogram instance.\n\nIf no `reporter` is provided, results are printed to the console.\n\n```js\nconst { Suite } = require('bench-node');\nconst suite = new Suite();\n```\n\nIf you don't want results to be printed to the console, `false` and `null` can be used\n\n```js\nconst { Suite } = require('bench-node');\nconst suite = new Suite({ reporter: false });\n```\n\n### `suite.add(name[, options], fn)`\n\n* `name` {string} The name of the benchmark, displayed when reporting results.\n* `options` {Object} Configuration options for the benchmark. Supported properties:\n  * `minTime` {number} Minimum duration for the benchmark to run. **Default:** `0.05` seconds.\n  * `maxTime` {number} Maximum duration for the benchmark to run. **Default:** `0.5` seconds.\n  * `repeatSuite` {number} Number of times to repeat benchmark to run. **Default:** `1` times.\n  * `minSamples` {number} Number minimum of samples the each round. **Default:** `10` samples.\n* `fn` {Function|AsyncFunction} The benchmark function. Can be synchronous or asynchronous. \n* Returns: {Suite}\n\nAdds a benchmark function to the suite.\n\n```bash\n$ node --allow-natives-syntax my-benchmark.js\nUsing delete property x 5,853,505 ops/sec (10 runs sampled) min..max=(169ns ... 171ns)\n```\n\n### `suite.run()`\n\n* Returns: `{Promise\u003cArray\u003cObject\u003e\u003e}` An array of benchmark results, each containing:\n  * `opsSec` {number} Operations per second.\n  * `iterations` {number} Number of executions of `fn`.\n  * `histogram` {Histogram} Histogram of benchmark iterations.\n  * `name` {string} Benchmark name.\n  * `plugins` {Object} Object with plugin results if any plugins are active.\n\nRuns all added benchmarks and returns the results.\n\n## Plugins\n\nPlugins extend the functionality of the benchmark module. \n\nSee [Plugins](./doc/Plugins.md) for details.\n\n### Plugin Methods\n\n- **`isSupported()`**: Checks if the plugin can run in the current environment.\n- **`beforeClockTemplate(varNames)`**: Injects code before the benchmark starts. Returns an array with:\n  * `Code` {string} JavaScript code to execute.\n  * `Wrapper` {string} (optional) Function to wrap the benchmark function.\n- **`afterClockTemplate(varNames)`**: Injects code after the benchmark finishes. Returns an array with:\n  * `Code` {string} JavaScript code to execute.\n- **`onCompleteBenchmark(result)`**: Called when the benchmark completes, allowing plugins to process results.\n- **`toString()`**: Returns a string identifier for the plugin.\n\n### Example Plugin\n\n```js\nclass V8OptimizeOnNextCallPlugin {\n  isSupported() {\n    try {\n      new Function(`%OptimizeFunctionOnNextCall(() =\u003e {})`)();\n      return true;\n    } catch (e) {\n      return false;\n    }\n  }\n\n  beforeClockTemplate({ awaitOrEmpty, bench }) {\n    let code = '';\n    code += `%OptimizeFunctionOnNextCall(${bench}.fn);\\n`;\n    code += `${awaitOrEmpty}${bench}.fn();\\n`;\n    code += `${awaitOrEmpty}${bench}.fn();\\n`;\n    return [code];\n  }\n\n  toString() {\n    return 'V8OptimizeOnNextCallPlugin';\n  }\n}\n```\n\n## Using Reporter\n\nThis module exports two reporters that control how benchmark results are displayed:\na detailed `textReport` for statistical analysis, and a visual `chartReport` that\ndisplays a bar graph in the terminal.\n\n### `textReport` (Default)\n\nThe `textReport` is the default reporter, which provides simple statistical information\nabout each benchmark result. It includes the number of operations per second, the number\nof runs sampled, min...max, and enabled plugins.\n\n**Example Output**:\n\n```\nsingle with matcher                           x 710,248 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(1.40us...1.42us)\nmultiple replaces                             x 604,713 ops/sec (11 runs sampled) v8-never-optimize=true min..max=(1.64us...1.70us)\n```\n\nHere’s how you can explicitly pass it as a reporter:\n\n```cjs\nconst { Suite, textReport } = require('bench-node');\n\nconst suite = new Suite({\n  reporter: textReport, // Optional, since this is the default\n});\n```\n\n### `chartReport`\n\nThe `chartReport` reporter provides a graphical representation of benchmark\nresults in the form of a bar chart, making it easier to visualize the relative\nperformance of each benchmark. It scales the bars based on the highest operations\nper second (ops/sec) value, and displays the results incrementally as they are collected.\n\nExample output:\n\n```\nNode.js version: v23.6.1\nPlatform: darwin arm64\nCPU Cores: 8 vCPUs | 16.0GB Mem\n\nsingle with matcher                           | ██████████████████████████████ | 709,321 ops/sec | 10 samples\nmultiple replaces                             | ██████████████████████████---- | 606,401 ops/sec | 11 samples\n```\n\nUsage:\n\n```cjs\nconst { Suite, chartReport } = require('bench-node');\n\nconst suite = new Suite({\n  reporter: chartReport,\n});\n```\n\n### `htmlReport`\n\nThe `htmlReport` generates an interactive HTML visualization of benchmark results.\nIt transforms benchmark data into a visual format, such as speed circle animations,\nmaking it easier to interpret and share performance insights.\n\nExample output:\n\nhttps://github.com/user-attachments/assets/b2b98175-6648-4af4-8319-63f3ebbc729e\n\nUsage:\n\n```cjs\nconst { Suite, htmlReport } = require('bench-node');\n\nconst suite = new Suite({\n  reporter: htmlReport,\n});\n```\n\n### `jsonReport`\n\nThe `jsonReport` plugin provides benchmark results in **JSON format**.  \nIt includes key performance metrics—such as `opsSec`, `runsSampled`, `min`\nand `max` times, and any reporter data from your **plugins**—so you can easily\nstore, parse, or share the information.\n\nExample output:\n\n```json\n[\n  {\n    \"name\": \"single with matcher\",\n    \"opsSec\": 180000,\n    \"runsSampled\": 50,\n    \"min\": \"13.20μs\",\n    \"max\": \"82.57μs\",\n    \"plugins\": []\n  },\n  {\n    \"name\": \"Multiple replaces\",\n    \"opsSec\": 170000,\n    \"runsSampled\": 50,\n    \"min\": \"15.31μs\",\n    \"max\": \"77.49μs\",\n    \"plugins\": []\n  }\n]\n```\n\n**Usage:**\n\n```cjs\nconst { Suite, jsonReport } = require('bench-node');\n\nconst suite = new Suite({\n  reporter: jsonReport,\n});\n```\n\n### CSV Reporter\n\nThe `csvReport` plugin generates benchmark results in CSV format.\nIt includes columns for key performance metrics like `ops/sec`, `samples`, `min` and `max` times,\nas well as any reporter data provided by your plugins.\n\nExample output:\n\n```csv\nname,ops/sec,samples,plugins,min,max\nUsing delete property,\"7,732,517\",10,v8-never-optimize=true,127.91ns,129.95ns\nUsing delete property (proto: null),\"24,636,631\",10,v8-never-optimize=true,39.57ns,40.91ns\nUsing delete property (cached proto: null),\"7,497,893\",11,v8-never-optimize=true,132.25ns,134.89ns\nUsing undefined assignment,\"132,093,600\",11,v8-never-optimize=true,7.53ns,7.64ns\nUsing undefined assignment (proto: null),\"28,231,374\",9,v8-never-optimize=true,35.27ns,35.42ns\nUsing undefined property (cached proto: null),\"60,843,193\",10,v8-never-optimize=true,16.24ns,16.65ns\n[Managed] Using undefined property (cached proto: null),\"35,394,060\",10,v8-never-optimize=true,27.90ns,28.54ns\n```\n\n**Usage:**\n\n```cjs\nconst { Suite, csvReport } = require('bench-node');\n\nconst suite = new Suite({\n  reporter: csvReport,\n});\n```\n\n### Custom Reporter\n\nCustomize data reporting by providing a `reporter` function when creating the `Suite`:\n\n```js\nconst { Suite } = require('bench-node');\n\nfunction reporter(results) {\n  for (const result of results) {\n    console.log(`Benchmark: ${result.name}`);\n    console.log(`Operations per second: ${result.opsSec}`);\n    console.log(`Iterations: ${result.iterations}`);\n    console.log(`Histogram: ${result.histogram}`);\n  }\n}\n\nconst suite = new Suite({ reporter });\n\nsuite.add('Using delete to remove property from object', () =\u003e {\n  const data = { x: 1, y: 2, z: 3 };\n  delete data.y;\n\n  data.x;\n  data.y;\n  data.z;\n});\n\nsuite.run();\n```\n\n```bash\n$ node --allow-natives-syntax my-benchmark.js\nBenchmark: Using delete to remove property from object - 6,032,212 ops/sec\n```\n\n## Setup and Teardown\n\nControl the benchmark function's setup and teardown using the timer argument:\n\n```js\nconst { Suite } = require('bench-node');\nconst { readFileSync, writeFileSync, rmSync } = require('node:fs');\n\nconst suite = new Suite();\n\nsuite.add('readFileSync', (timer) =\u003e {\n  const randomFile = Date.now();\n  const filePath = `./${randomFile}.txt`;\n  writeFileSync(filePath, Math.random().toString());\n\n  timer.start();\n  readFileSync(filePath, 'utf8');\n  timer.end();\n\n  rmSync(filePath);\n}).run();\n```\n\nFor advanced setups, use the timer argument to start and end timing explicitly:\n\n```js\nconst { Suite } = require('bench-node');\nconst { readFileSync, writeFileSync, rmSync } = require('node:fs');\n\nconst suite = new Suite();\n\nsuite.add('readFileSync', (timer) =\u003e {\n  const randomFile = Date.now();\n  const filePath = `./${randomFile}.txt`;\n  writeFileSync(filePath, Math.random().toString());\n\n  timer.start();\n  for (let i = 0; i \u003c timer.count; i++) {\n    readFileSync(filePath, 'utf8');\n  }\n  timer.end(timer.count);\n\n  rmSync(filePath);\n});\n\nsuite.run();\n```\n\n\u003e [!WARNING]\n\u003e When using the `timer`, the setup will also be deoptimized.\n\u003e As a result, if you compare this approach with one that uses functions outside\n\u003e the benchmark function, the results may not match.\n\u003e See: [Deleting Properties Example](./examples/deleting-properties/node.js).\n\nEnsure you call `.start()` and `.end()` methods when using the timer argument, or an `ERR_BENCHMARK_MISSING_OPERATION` error will be thrown.\n\n### Managed Benchmarks\n\nIn regular benchmarks (when `timer` is not used), you run the benchmarked function in a loop,\nand the timing is managed implicitly.\nThis means each iteration of the benchmarked function is measured directly.\nThe downside is that optimizations like inlining or caching might affect the timing, especially for fast operations.\n\nExample:\n\n```cjs\nsuite.add('Using includes', () =\u003e {\n  const text = 'text/html,...';\n  const r = text.includes('application/json');\n});\n```\n\nHere, `%DoNotOptimize` is being called inside the loop for regular benchmarks (assuming V8NeverOptimizePlugin is being used),\nensuring that the operation is not overly optimized within each loop iteration.\nThis prevents V8 from optimizing away the operation (e.g., skipping certain steps because the result is not used or the function is too trivial).\n\nManaged benchmarks explicitly handle timing through `start()` and `end()` calls around the benchmarked code.\nThis encapsulates the entire set of iterations in one timed block,\nwhich can result in tighter measurement with less overhead.\nHowever, it can lead to over-optimistic results, especially if the timer’s start and stop calls are placed outside of the loop,\nallowing V8 to over-optimize the entire block.\n\nExample:\n\n```cjs\nsuite.add('[Managed] Using includes', (timer) =\u003e {\n  timer.start();\n  for (let i = 0; i \u003c timer.count; i++) {\n    const text = 'text/html,...';\n    const r = text.includes('application/json');\n    assert.ok(r);  // Ensure the result is used so it doesn't get V8 optimized away\n  }\n  timer.end(timer.count);\n});\n```\n\nIn this case, `%DoNotOptimize` is being applied outside the loop, so it does not protect each iteration from\nexcessive optimization. This can result in higher operation counts because V8 might optimize away repetitive tasks.\nThat's why an `assert.ok(r)` has been used. To avoid V8 optimizing the entire block as the `r` var was not being used.\n\n\u003e [!NOTE]\n\u003e V8 assumptions can change any time soon. Therefore, it's crucial to investigate\n\u003e results between versions of V8/Node.js.\n\n### Worker Threads\n\n\u003e Stability: 1.0 (Experimental)\n\n`bench-node` provides experimental support for **Worker Threads**. When you set `useWorkers: true`,\nthe library runs each benchmark in a separate worker thread, ensuring that one benchmark\ndoes not affect another. Usage is straightforward:\n\n```cjs\nconst suite = new Suite({\n  useWorkers: true,\n});\n```\n\n## Writing JavaScript Mistakes\n\nWhen working on JavaScript micro-benchmarks, it’s easy to forget that modern engines use\nmultiple tiers of Just-In-Time (JIT) compilation and sometimes even entirely different\noptimizations. The results you get from a simple timing loop often aren’t representative\nof how your code will behave under real-world conditions, especially once the browser or\nruntime has adjusted for frequent function calls. Caching, tail call optimizations,\nand hidden class transformations can all distort your measurements, leading to overblown\nclaims about performance improvements that might never materialize in production.\n\nThat’s why **bench-node** was created—to provide a stable and consistent way to compare\nsmall snippets of code. By default, it tells V8 to never optimize your code with a\nsnippet like `%NeverOptimizeFunction(DoNotOptimize)`, ensuring the JIT compiler doesn’t\nremove dead code. However, even this approach can’t fully replicate real-world scenarios\nin which V8 optimizations and unpredictable workloads impact performance. Think of\nbench-node as a helpful tool for quick comparisons rather than a guarantee of what you’ll\nsee in production.\n\n\n[build-img]:https://github.com/RafaelGSS/bench-node/actions/workflows/release.yml/badge.svg\n\n[build-url]:https://github.com/RafaelGSS/bench-node/actions/workflows/release.yml\n\n[downloads-img]:https://img.shields.io/npm/dt/bench-node\n\n[downloads-url]:https://www.npmtrends.com/bench-node\n\n[npm-img]:https://img.shields.io/npm/v/bench-node\n\n[npm-url]:https://www.npmjs.com/package/bench-node\n\n[issues-img]:https://img.shields.io/github/issues/RafaelGSS/bench-node\n\n[issues-url]:https://github.com/RafaelGSS/bench-node/issues\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafaelgss%2Fbench-node","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frafaelgss%2Fbench-node","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafaelgss%2Fbench-node/lists"}