11cb0ef41Sopenharmony_ci# How to write and run benchmarks in Node.js core
21cb0ef41Sopenharmony_ci
31cb0ef41Sopenharmony_ci## Table of contents
41cb0ef41Sopenharmony_ci
51cb0ef41Sopenharmony_ci* [Prerequisites](#prerequisites)
61cb0ef41Sopenharmony_ci  * [HTTP benchmark requirements](#http-benchmark-requirements)
71cb0ef41Sopenharmony_ci  * [HTTPS benchmark requirements](#https-benchmark-requirements)
81cb0ef41Sopenharmony_ci  * [HTTP/2 benchmark requirements](#http2-benchmark-requirements)
91cb0ef41Sopenharmony_ci  * [Benchmark analysis requirements](#benchmark-analysis-requirements)
101cb0ef41Sopenharmony_ci* [Running benchmarks](#running-benchmarks)
111cb0ef41Sopenharmony_ci  * [Running individual benchmarks](#running-individual-benchmarks)
121cb0ef41Sopenharmony_ci  * [Running all benchmarks](#running-all-benchmarks)
131cb0ef41Sopenharmony_ci  * [Filtering benchmarks](#filtering-benchmarks)
141cb0ef41Sopenharmony_ci  * [Comparing Node.js versions](#comparing-nodejs-versions)
151cb0ef41Sopenharmony_ci  * [Comparing parameters](#comparing-parameters)
161cb0ef41Sopenharmony_ci  * [Running benchmarks on the CI](#running-benchmarks-on-the-ci)
171cb0ef41Sopenharmony_ci* [Creating a benchmark](#creating-a-benchmark)
181cb0ef41Sopenharmony_ci  * [Basics of a benchmark](#basics-of-a-benchmark)
191cb0ef41Sopenharmony_ci  * [Creating an HTTP benchmark](#creating-an-http-benchmark)
201cb0ef41Sopenharmony_ci
211cb0ef41Sopenharmony_ci## Prerequisites
221cb0ef41Sopenharmony_ci
231cb0ef41Sopenharmony_ciBasic Unix tools are required for some benchmarks.
241cb0ef41Sopenharmony_ci[Git for Windows][git-for-windows] includes Git Bash and the necessary tools,
251cb0ef41Sopenharmony_ciwhich need to be included in the global Windows `PATH`.
261cb0ef41Sopenharmony_ci
271cb0ef41Sopenharmony_ci### HTTP benchmark requirements
281cb0ef41Sopenharmony_ci
291cb0ef41Sopenharmony_ciMost of the HTTP benchmarks require a benchmarker to be installed. This can be
301cb0ef41Sopenharmony_cieither [`wrk`][wrk] or [`autocannon`][autocannon].
311cb0ef41Sopenharmony_ci
321cb0ef41Sopenharmony_ci`Autocannon` is a Node.js script that can be installed using
331cb0ef41Sopenharmony_ci`npm install -g autocannon`. It will use the Node.js executable that is in the
341cb0ef41Sopenharmony_cipath. In order to compare two HTTP benchmark runs, make sure that the
351cb0ef41Sopenharmony_ciNode.js version in the path is not altered.
361cb0ef41Sopenharmony_ci
371cb0ef41Sopenharmony_ci`wrk` may be available through one of the available package managers. If not,
381cb0ef41Sopenharmony_ciit can be easily built [from source][wrk] via `make`.
391cb0ef41Sopenharmony_ci
401cb0ef41Sopenharmony_ciBy default, `wrk` will be used as the benchmarker. If it is not available,
411cb0ef41Sopenharmony_ci`autocannon` will be used in its place. When creating an HTTP benchmark, the
421cb0ef41Sopenharmony_cibenchmarker to be used should be specified by providing it as an argument:
431cb0ef41Sopenharmony_ci
441cb0ef41Sopenharmony_ci`node benchmark/run.js --set benchmarker=autocannon http`
451cb0ef41Sopenharmony_ci
461cb0ef41Sopenharmony_ci`node benchmark/http/simple.js benchmarker=autocannon`
471cb0ef41Sopenharmony_ci
481cb0ef41Sopenharmony_ci#### HTTPS benchmark requirements
491cb0ef41Sopenharmony_ci
501cb0ef41Sopenharmony_ciTo run the `https` benchmarks, one of `autocannon` or `wrk` benchmarkers must
511cb0ef41Sopenharmony_cibe used.
521cb0ef41Sopenharmony_ci
531cb0ef41Sopenharmony_ci`node benchmark/https/simple.js benchmarker=autocannon`
541cb0ef41Sopenharmony_ci
551cb0ef41Sopenharmony_ci#### HTTP/2 benchmark requirements
561cb0ef41Sopenharmony_ci
571cb0ef41Sopenharmony_ciTo run the `http2` benchmarks, the `h2load` benchmarker must be used. The
581cb0ef41Sopenharmony_ci`h2load` tool is a component of the `nghttp2` project and may be installed
591cb0ef41Sopenharmony_cifrom [nghttp2.org][] or built from source.
601cb0ef41Sopenharmony_ci
611cb0ef41Sopenharmony_ci`node benchmark/http2/simple.js benchmarker=h2load`
621cb0ef41Sopenharmony_ci
631cb0ef41Sopenharmony_ci### Benchmark analysis requirements
641cb0ef41Sopenharmony_ci
651cb0ef41Sopenharmony_ciTo analyze the results statistically, you can use either the
661cb0ef41Sopenharmony_ci[node-benchmark-compare][] tool or the R script `benchmark/compare.R`.
671cb0ef41Sopenharmony_ci
681cb0ef41Sopenharmony_ci[node-benchmark-compare][] is a Node.js script that can be installed with
691cb0ef41Sopenharmony_ci`npm install -g node-benchmark-compare`.
701cb0ef41Sopenharmony_ci
711cb0ef41Sopenharmony_ciTo draw comparison plots when analyzing the results, `R` must be installed.
721cb0ef41Sopenharmony_ciUse one of the available package managers or download it from
731cb0ef41Sopenharmony_ci<https://www.r-project.org/>.
741cb0ef41Sopenharmony_ci
751cb0ef41Sopenharmony_ciThe R packages `ggplot2` and `plyr` are also used and can be installed using
761cb0ef41Sopenharmony_cithe R REPL.
771cb0ef41Sopenharmony_ci
781cb0ef41Sopenharmony_ci```console
791cb0ef41Sopenharmony_ci$ R
801cb0ef41Sopenharmony_ciinstall.packages("ggplot2")
811cb0ef41Sopenharmony_ciinstall.packages("plyr")
821cb0ef41Sopenharmony_ci```
831cb0ef41Sopenharmony_ci
841cb0ef41Sopenharmony_ciIf a message states that a CRAN mirror must be selected first, specify a mirror
851cb0ef41Sopenharmony_ciwith the `repo` parameter.
861cb0ef41Sopenharmony_ci
871cb0ef41Sopenharmony_ci```r
881cb0ef41Sopenharmony_ciinstall.packages("ggplot2", repo="http://cran.us.r-project.org")
891cb0ef41Sopenharmony_ci```
901cb0ef41Sopenharmony_ci
911cb0ef41Sopenharmony_ciOf course, use an appropriate mirror based on location.
921cb0ef41Sopenharmony_ciA list of mirrors is [located here](https://cran.r-project.org/mirrors.html).
931cb0ef41Sopenharmony_ci
941cb0ef41Sopenharmony_ci## Running benchmarks
951cb0ef41Sopenharmony_ci
961cb0ef41Sopenharmony_ci### Running individual benchmarks
971cb0ef41Sopenharmony_ci
981cb0ef41Sopenharmony_ciThis can be useful for debugging a benchmark or doing a quick performance
991cb0ef41Sopenharmony_cimeasure. But it does not provide the statistical information to make any
1001cb0ef41Sopenharmony_ciconclusions about the performance.
1011cb0ef41Sopenharmony_ci
1021cb0ef41Sopenharmony_ciIndividual benchmarks can be executed by simply executing the benchmark script
1031cb0ef41Sopenharmony_ciwith node.
1041cb0ef41Sopenharmony_ci
1051cb0ef41Sopenharmony_ci```console
1061cb0ef41Sopenharmony_ci$ node benchmark/buffers/buffer-tostring.js
1071cb0ef41Sopenharmony_ci
1081cb0ef41Sopenharmony_cibuffers/buffer-tostring.js n=10000000 len=0 arg=true: 62710590.393305704
1091cb0ef41Sopenharmony_cibuffers/buffer-tostring.js n=10000000 len=1 arg=true: 9178624.591787899
1101cb0ef41Sopenharmony_cibuffers/buffer-tostring.js n=10000000 len=64 arg=true: 7658962.8891432695
1111cb0ef41Sopenharmony_cibuffers/buffer-tostring.js n=10000000 len=1024 arg=true: 4136904.4060201733
1121cb0ef41Sopenharmony_cibuffers/buffer-tostring.js n=10000000 len=0 arg=false: 22974354.231509723
1131cb0ef41Sopenharmony_cibuffers/buffer-tostring.js n=10000000 len=1 arg=false: 11485945.656765845
1141cb0ef41Sopenharmony_cibuffers/buffer-tostring.js n=10000000 len=64 arg=false: 8718280.70650129
1151cb0ef41Sopenharmony_cibuffers/buffer-tostring.js n=10000000 len=1024 arg=false: 4103857.0726124765
1161cb0ef41Sopenharmony_ci```
1171cb0ef41Sopenharmony_ci
1181cb0ef41Sopenharmony_ciEach line represents a single benchmark with parameters specified as
1191cb0ef41Sopenharmony_ci`${variable}=${value}`. Each configuration combination is executed in a separate
1201cb0ef41Sopenharmony_ciprocess. This ensures that benchmark results aren't affected by the execution
1211cb0ef41Sopenharmony_ciorder due to V8 optimizations. **The last number is the rate of operations
1221cb0ef41Sopenharmony_cimeasured in ops/sec (higher is better).**
1231cb0ef41Sopenharmony_ci
1241cb0ef41Sopenharmony_ciFurthermore a subset of the configurations can be specified, by setting them in
1251cb0ef41Sopenharmony_cithe process arguments:
1261cb0ef41Sopenharmony_ci
1271cb0ef41Sopenharmony_ci```console
1281cb0ef41Sopenharmony_ci$ node benchmark/buffers/buffer-tostring.js len=1024
1291cb0ef41Sopenharmony_ci
1301cb0ef41Sopenharmony_cibuffers/buffer-tostring.js n=10000000 len=1024 arg=true: 3498295.68561504
1311cb0ef41Sopenharmony_cibuffers/buffer-tostring.js n=10000000 len=1024 arg=false: 3783071.1678948295
1321cb0ef41Sopenharmony_ci```
1331cb0ef41Sopenharmony_ci
1341cb0ef41Sopenharmony_ci### Running all benchmarks
1351cb0ef41Sopenharmony_ci
1361cb0ef41Sopenharmony_ciSimilar to running individual benchmarks, a group of benchmarks can be executed
1371cb0ef41Sopenharmony_ciby using the `run.js` tool. To see how to use this script,
1381cb0ef41Sopenharmony_cirun `node benchmark/run.js`. Again this does not provide the statistical
1391cb0ef41Sopenharmony_ciinformation to make any conclusions.
1401cb0ef41Sopenharmony_ci
1411cb0ef41Sopenharmony_ci```console
1421cb0ef41Sopenharmony_ci$ node benchmark/run.js assert
1431cb0ef41Sopenharmony_ci
1441cb0ef41Sopenharmony_ciassert/deepequal-buffer.js
1451cb0ef41Sopenharmony_ciassert/deepequal-buffer.js method="deepEqual" strict=0 len=100 n=20000: 773,200.4995493788
1461cb0ef41Sopenharmony_ciassert/deepequal-buffer.js method="notDeepEqual" strict=0 len=100 n=20000: 964,411.712953848
1471cb0ef41Sopenharmony_ci...
1481cb0ef41Sopenharmony_ci
1491cb0ef41Sopenharmony_ciassert/deepequal-map.js
1501cb0ef41Sopenharmony_ciassert/deepequal-map.js method="deepEqual_primitiveOnly" strict=0 len=500 n=500: 20,445.06368453332
1511cb0ef41Sopenharmony_ciassert/deepequal-map.js method="deepEqual_objectOnly" strict=0 len=500 n=500: 1,393.3481642240833
1521cb0ef41Sopenharmony_ci...
1531cb0ef41Sopenharmony_ci
1541cb0ef41Sopenharmony_ciassert/deepequal-object.js
1551cb0ef41Sopenharmony_ciassert/deepequal-object.js method="deepEqual" strict=0 size=100 n=5000: 1,053.1950937538475
1561cb0ef41Sopenharmony_ciassert/deepequal-object.js method="notDeepEqual" strict=0 size=100 n=5000: 9,734.193251965213
1571cb0ef41Sopenharmony_ci...
1581cb0ef41Sopenharmony_ci```
1591cb0ef41Sopenharmony_ci
1601cb0ef41Sopenharmony_ciIt is possible to execute more groups by adding extra process arguments.
1611cb0ef41Sopenharmony_ci
1621cb0ef41Sopenharmony_ci```console
1631cb0ef41Sopenharmony_ci$ node benchmark/run.js assert async_hooks
1641cb0ef41Sopenharmony_ci```
1651cb0ef41Sopenharmony_ci
1661cb0ef41Sopenharmony_ci#### Filtering benchmarks
1671cb0ef41Sopenharmony_ci
1681cb0ef41Sopenharmony_ci`benchmark/run.js` and `benchmark/compare.js` have `--filter pattern` and
1691cb0ef41Sopenharmony_ci`--exclude pattern` options, which can be used to run a subset of benchmarks or
1701cb0ef41Sopenharmony_cito exclude specific benchmarks from the execution, respectively.
1711cb0ef41Sopenharmony_ci
1721cb0ef41Sopenharmony_ci```console
1731cb0ef41Sopenharmony_ci$ node benchmark/run.js --filter "deepequal-b" assert
1741cb0ef41Sopenharmony_ci
1751cb0ef41Sopenharmony_ciassert/deepequal-buffer.js
1761cb0ef41Sopenharmony_ciassert/deepequal-buffer.js method="deepEqual" strict=0 len=100 n=20000: 773,200.4995493788
1771cb0ef41Sopenharmony_ciassert/deepequal-buffer.js method="notDeepEqual" strict=0 len=100 n=20000: 964,411.712953848
1781cb0ef41Sopenharmony_ci
1791cb0ef41Sopenharmony_ci$ node benchmark/run.js --exclude "deepequal-b" assert
1801cb0ef41Sopenharmony_ci
1811cb0ef41Sopenharmony_ciassert/deepequal-map.js
1821cb0ef41Sopenharmony_ciassert/deepequal-map.js method="deepEqual_primitiveOnly" strict=0 len=500 n=500: 20,445.06368453332
1831cb0ef41Sopenharmony_ciassert/deepequal-map.js method="deepEqual_objectOnly" strict=0 len=500 n=500: 1,393.3481642240833
1841cb0ef41Sopenharmony_ci...
1851cb0ef41Sopenharmony_ci
1861cb0ef41Sopenharmony_ciassert/deepequal-object.js
1871cb0ef41Sopenharmony_ciassert/deepequal-object.js method="deepEqual" strict=0 size=100 n=5000: 1,053.1950937538475
1881cb0ef41Sopenharmony_ciassert/deepequal-object.js method="notDeepEqual" strict=0 size=100 n=5000: 9,734.193251965213
1891cb0ef41Sopenharmony_ci...
1901cb0ef41Sopenharmony_ci```
1911cb0ef41Sopenharmony_ci
1921cb0ef41Sopenharmony_ci`--filter` and `--exclude` can be repeated to provide multiple patterns.
1931cb0ef41Sopenharmony_ci
1941cb0ef41Sopenharmony_ci```console
1951cb0ef41Sopenharmony_ci$ node benchmark/run.js --filter "deepequal-b" --filter "deepequal-m" assert
1961cb0ef41Sopenharmony_ci
1971cb0ef41Sopenharmony_ciassert/deepequal-buffer.js
1981cb0ef41Sopenharmony_ciassert/deepequal-buffer.js method="deepEqual" strict=0 len=100 n=20000: 773,200.4995493788
1991cb0ef41Sopenharmony_ciassert/deepequal-buffer.js method="notDeepEqual" strict=0 len=100 n=20000: 964,411.712953848
2001cb0ef41Sopenharmony_ci
2011cb0ef41Sopenharmony_ciassert/deepequal-map.js
2021cb0ef41Sopenharmony_ciassert/deepequal-map.js method="deepEqual_primitiveOnly" strict=0 len=500 n=500: 20,445.06368453332
2031cb0ef41Sopenharmony_ciassert/deepequal-map.js method="deepEqual_objectOnly" strict=0 len=500 n=500: 1,393.3481642240833
2041cb0ef41Sopenharmony_ci
2051cb0ef41Sopenharmony_ci$ node benchmark/run.js --exclude "deepequal-b" --exclude "deepequal-m" assert
2061cb0ef41Sopenharmony_ci
2071cb0ef41Sopenharmony_ciassert/deepequal-object.js
2081cb0ef41Sopenharmony_ciassert/deepequal-object.js method="deepEqual" strict=0 size=100 n=5000: 1,053.1950937538475
2091cb0ef41Sopenharmony_ciassert/deepequal-object.js method="notDeepEqual" strict=0 size=100 n=5000: 9,734.193251965213
2101cb0ef41Sopenharmony_ci...
2111cb0ef41Sopenharmony_ci
2121cb0ef41Sopenharmony_ciassert/deepequal-prims-and-objs-big-array-set.js
2131cb0ef41Sopenharmony_ciassert/deepequal-prims-and-objs-big-array-set.js method="deepEqual_Array" strict=0 len=20000 n=25 primitive="string": 865.2977195251661
2141cb0ef41Sopenharmony_ciassert/deepequal-prims-and-objs-big-array-set.js method="notDeepEqual_Array" strict=0 len=20000 n=25 primitive="string": 827.8297281403861
2151cb0ef41Sopenharmony_ciassert/deepequal-prims-and-objs-big-array-set.js method="deepEqual_Set" strict=0 len=20000 n=25 primitive="string": 28,826.618268696366
2161cb0ef41Sopenharmony_ci...
2171cb0ef41Sopenharmony_ci```
2181cb0ef41Sopenharmony_ci
2191cb0ef41Sopenharmony_ciIf `--filter` and `--exclude` are used together, `--filter` is applied first,
2201cb0ef41Sopenharmony_ciand `--exclude` is applied on the result of `--filter`:
2211cb0ef41Sopenharmony_ci
2221cb0ef41Sopenharmony_ci```console
2231cb0ef41Sopenharmony_ci$ node benchmark/run.js --filter "bench-" process
2241cb0ef41Sopenharmony_ci
2251cb0ef41Sopenharmony_ciprocess/bench-env.js
2261cb0ef41Sopenharmony_ciprocess/bench-env.js operation="get" n=1000000: 2,356,946.0770617095
2271cb0ef41Sopenharmony_ciprocess/bench-env.js operation="set" n=1000000: 1,295,176.3266261867
2281cb0ef41Sopenharmony_ciprocess/bench-env.js operation="enumerate" n=1000000: 24,592.32231990992
2291cb0ef41Sopenharmony_ciprocess/bench-env.js operation="query" n=1000000: 3,625,787.2150573144
2301cb0ef41Sopenharmony_ciprocess/bench-env.js operation="delete" n=1000000: 1,521,131.5742806569
2311cb0ef41Sopenharmony_ci
2321cb0ef41Sopenharmony_ciprocess/bench-hrtime.js
2331cb0ef41Sopenharmony_ciprocess/bench-hrtime.js type="raw" n=1000000: 13,178,002.113936031
2341cb0ef41Sopenharmony_ciprocess/bench-hrtime.js type="diff" n=1000000: 11,585,435.712423025
2351cb0ef41Sopenharmony_ciprocess/bench-hrtime.js type="bigint" n=1000000: 13,342,884.703919787
2361cb0ef41Sopenharmony_ci
2371cb0ef41Sopenharmony_ci$ node benchmark/run.js --filter "bench-" --exclude "hrtime" process
2381cb0ef41Sopenharmony_ci
2391cb0ef41Sopenharmony_ciprocess/bench-env.js
2401cb0ef41Sopenharmony_ciprocess/bench-env.js operation="get" n=1000000: 2,356,946.0770617095
2411cb0ef41Sopenharmony_ciprocess/bench-env.js operation="set" n=1000000: 1,295,176.3266261867
2421cb0ef41Sopenharmony_ciprocess/bench-env.js operation="enumerate" n=1000000: 24,592.32231990992
2431cb0ef41Sopenharmony_ciprocess/bench-env.js operation="query" n=1000000: 3,625,787.2150573144
2441cb0ef41Sopenharmony_ciprocess/bench-env.js operation="delete" n=1000000: 1,521,131.5742806569
2451cb0ef41Sopenharmony_ci```
2461cb0ef41Sopenharmony_ci
2471cb0ef41Sopenharmony_ci### Comparing Node.js versions
2481cb0ef41Sopenharmony_ci
2491cb0ef41Sopenharmony_ciTo compare the effect of a new Node.js version use the `compare.js` tool. This
2501cb0ef41Sopenharmony_ciwill run each benchmark multiple times, making it possible to calculate
2511cb0ef41Sopenharmony_cistatistics on the performance measures. To see how to use this script,
2521cb0ef41Sopenharmony_cirun `node benchmark/compare.js`.
2531cb0ef41Sopenharmony_ci
2541cb0ef41Sopenharmony_ciAs an example on how to check for a possible performance improvement, the
2551cb0ef41Sopenharmony_ci[#5134](https://github.com/nodejs/node/pull/5134) pull request will be used as
2561cb0ef41Sopenharmony_cian example. This pull request _claims_ to improve the performance of the
2571cb0ef41Sopenharmony_ci`node:string_decoder` module.
2581cb0ef41Sopenharmony_ci
2591cb0ef41Sopenharmony_ciFirst build two versions of Node.js, one from the `main` branch (here called
2601cb0ef41Sopenharmony_ci`./node-main`) and another with the pull request applied (here called
2611cb0ef41Sopenharmony_ci`./node-pr-5134`).
2621cb0ef41Sopenharmony_ci
2631cb0ef41Sopenharmony_ciTo run multiple compiled versions in parallel you need to copy the output of the
2641cb0ef41Sopenharmony_cibuild: `cp ./out/Release/node ./node-main`. Check out the following example:
2651cb0ef41Sopenharmony_ci
2661cb0ef41Sopenharmony_ci```console
2671cb0ef41Sopenharmony_ci$ git checkout main
2681cb0ef41Sopenharmony_ci$ ./configure && make -j4
2691cb0ef41Sopenharmony_ci$ cp ./out/Release/node ./node-main
2701cb0ef41Sopenharmony_ci
2711cb0ef41Sopenharmony_ci$ git checkout pr-5134
2721cb0ef41Sopenharmony_ci$ ./configure && make -j4
2731cb0ef41Sopenharmony_ci$ cp ./out/Release/node ./node-pr-5134
2741cb0ef41Sopenharmony_ci```
2751cb0ef41Sopenharmony_ci
2761cb0ef41Sopenharmony_ciThe `compare.js` tool will then produce a csv file with the benchmark results.
2771cb0ef41Sopenharmony_ci
2781cb0ef41Sopenharmony_ci```console
2791cb0ef41Sopenharmony_ci$ node benchmark/compare.js --old ./node-main --new ./node-pr-5134 string_decoder > compare-pr-5134.csv
2801cb0ef41Sopenharmony_ci```
2811cb0ef41Sopenharmony_ci
2821cb0ef41Sopenharmony_ci_Tips: there are some useful options of `benchmark/compare.js`. For example,
2831cb0ef41Sopenharmony_ciif you want to compare the benchmark of a single script instead of a whole
2841cb0ef41Sopenharmony_cimodule, you can use the `--filter` option:_
2851cb0ef41Sopenharmony_ci
2861cb0ef41Sopenharmony_ci```console
2871cb0ef41Sopenharmony_ci  --new      ./new-node-binary  new node binary (required)
2881cb0ef41Sopenharmony_ci  --old      ./old-node-binary  old node binary (required)
2891cb0ef41Sopenharmony_ci  --runs     30                 number of samples
2901cb0ef41Sopenharmony_ci  --filter   pattern            string to filter benchmark scripts
2911cb0ef41Sopenharmony_ci  --set      variable=value     set benchmark variable (can be repeated)
2921cb0ef41Sopenharmony_ci  --no-progress                 don't show benchmark progress indicator
2931cb0ef41Sopenharmony_ci```
2941cb0ef41Sopenharmony_ci
2951cb0ef41Sopenharmony_ciFor analyzing the benchmark results, use [node-benchmark-compare][] or the R
2961cb0ef41Sopenharmony_ciscripts:
2971cb0ef41Sopenharmony_ci
2981cb0ef41Sopenharmony_ci* `benchmark/compare.R`
2991cb0ef41Sopenharmony_ci* `benchmark/bar.R`
3001cb0ef41Sopenharmony_ci
3011cb0ef41Sopenharmony_ci```console
3021cb0ef41Sopenharmony_ci$ node-benchmark-compare compare-pr-5134.csv # or cat compare-pr-5134.csv | Rscript benchmark/compare.R
3031cb0ef41Sopenharmony_ci
3041cb0ef41Sopenharmony_ci                                                                                             confidence improvement accuracy (*)    (**)   (***)
3051cb0ef41Sopenharmony_ci string_decoder/string-decoder.js n=2500000 chunkLen=16 inLen=128 encoding='ascii'                  ***     -3.76 %       ±1.36%  ±1.82%  ±2.40%
3061cb0ef41Sopenharmony_ci string_decoder/string-decoder.js n=2500000 chunkLen=16 inLen=128 encoding='utf8'                    **     -0.81 %       ±0.53%  ±0.71%  ±0.93%
3071cb0ef41Sopenharmony_ci string_decoder/string-decoder.js n=2500000 chunkLen=16 inLen=32 encoding='ascii'                   ***     -2.70 %       ±0.83%  ±1.11%  ±1.45%
3081cb0ef41Sopenharmony_ci string_decoder/string-decoder.js n=2500000 chunkLen=16 inLen=32 encoding='base64-ascii'            ***     -1.57 %       ±0.83%  ±1.11%  ±1.46%
3091cb0ef41Sopenharmony_ci...
3101cb0ef41Sopenharmony_ci```
3111cb0ef41Sopenharmony_ci
3121cb0ef41Sopenharmony_ciIn the output, _improvement_ is the relative improvement of the new version,
3131cb0ef41Sopenharmony_cihopefully this is positive. _confidence_ tells if there is enough
3141cb0ef41Sopenharmony_cistatistical evidence to validate the _improvement_. If there is enough evidence
3151cb0ef41Sopenharmony_cithen there will be at least one star (`*`), more stars is just better. **However
3161cb0ef41Sopenharmony_ciif there are no stars, then don't make any conclusions based on the
3171cb0ef41Sopenharmony_ci_improvement_.** Sometimes this is fine, for example if no improvements are
3181cb0ef41Sopenharmony_ciexpected, then there shouldn't be any stars.
3191cb0ef41Sopenharmony_ci
3201cb0ef41Sopenharmony_ci**A word of caution:** Statistics is not a foolproof tool. If a benchmark shows
3211cb0ef41Sopenharmony_cia statistical significant difference, there is a 5% risk that this
3221cb0ef41Sopenharmony_cidifference doesn't actually exist. For a single benchmark this is not an
3231cb0ef41Sopenharmony_ciissue. But when considering 20 benchmarks it's normal that one of them
3241cb0ef41Sopenharmony_ciwill show significance, when it shouldn't. A possible solution is to instead
3251cb0ef41Sopenharmony_ciconsider at least two stars (`**`) as the threshold, in that case the risk
3261cb0ef41Sopenharmony_ciis 1%. If three stars (`***`) is considered the risk is 0.1%. However this
3271cb0ef41Sopenharmony_cimay require more runs to obtain (can be set with `--runs`).
3281cb0ef41Sopenharmony_ci
3291cb0ef41Sopenharmony_ci_For the statistically minded, the script performs an [independent/unpaired
3301cb0ef41Sopenharmony_ci2-group t-test][t-test], with the null hypothesis that the performance is the
3311cb0ef41Sopenharmony_cisame for both versions. The confidence field will show a star if the p-value
3321cb0ef41Sopenharmony_ciis less than `0.05`._
3331cb0ef41Sopenharmony_ci
3341cb0ef41Sopenharmony_ciThe `compare.R` tool can additionally produce a box plot by using the
3351cb0ef41Sopenharmony_ci`--plot filename` option. In this case there are 48 different benchmark
3361cb0ef41Sopenharmony_cicombinations, and there may be a need to filter the csv file. This can be done
3371cb0ef41Sopenharmony_ciwhile benchmarking using the `--set` parameter (e.g. `--set encoding=ascii`) or
3381cb0ef41Sopenharmony_ciby filtering results afterwards using tools such as `sed` or `grep`. In the
3391cb0ef41Sopenharmony_ci`sed` case be sure to keep the first line since that contains the header
3401cb0ef41Sopenharmony_ciinformation.
3411cb0ef41Sopenharmony_ci
3421cb0ef41Sopenharmony_ci```console
3431cb0ef41Sopenharmony_ci$ cat compare-pr-5134.csv | sed '1p;/encoding='"'"ascii"'"'/!d' | Rscript benchmark/compare.R --plot compare-plot.png
3441cb0ef41Sopenharmony_ci
3451cb0ef41Sopenharmony_ci                                                                                      confidence improvement accuracy (*)    (**)   (***)
3461cb0ef41Sopenharmony_ci string_decoder/string-decoder.js n=2500000 chunkLen=16 inLen=128 encoding='ascii'           ***     -3.76 %       ±1.36%  ±1.82%  ±2.40%
3471cb0ef41Sopenharmony_ci string_decoder/string-decoder.js n=2500000 chunkLen=16 inLen=32 encoding='ascii'            ***     -2.70 %       ±0.83%  ±1.11%  ±1.45%
3481cb0ef41Sopenharmony_ci string_decoder/string-decoder.js n=2500000 chunkLen=16 inLen=4096 encoding='ascii'          ***     -4.06 %       ±0.31%  ±0.41%  ±0.54%
3491cb0ef41Sopenharmony_ci string_decoder/string-decoder.js n=2500000 chunkLen=256 inLen=1024 encoding='ascii'         ***     -1.42 %       ±0.58%  ±0.77%  ±1.01%
3501cb0ef41Sopenharmony_ci...
3511cb0ef41Sopenharmony_ci```
3521cb0ef41Sopenharmony_ci
3531cb0ef41Sopenharmony_ci![compare tool boxplot](doc_img/compare-boxplot.png)
3541cb0ef41Sopenharmony_ci
3551cb0ef41Sopenharmony_ci### Comparing parameters
3561cb0ef41Sopenharmony_ci
3571cb0ef41Sopenharmony_ciIt can be useful to compare the performance for different parameters, for
3581cb0ef41Sopenharmony_ciexample to analyze the time complexity.
3591cb0ef41Sopenharmony_ci
3601cb0ef41Sopenharmony_ciTo do this use the `scatter.js` tool, this will run a benchmark multiple times
3611cb0ef41Sopenharmony_ciand generate a csv with the results. To see how to use this script,
3621cb0ef41Sopenharmony_cirun `node benchmark/scatter.js`.
3631cb0ef41Sopenharmony_ci
3641cb0ef41Sopenharmony_ci```console
3651cb0ef41Sopenharmony_ci$ node benchmark/scatter.js benchmark/string_decoder/string-decoder.js > scatter.csv
3661cb0ef41Sopenharmony_ci```
3671cb0ef41Sopenharmony_ci
3681cb0ef41Sopenharmony_ciAfter generating the csv, a comparison table can be created using the
3691cb0ef41Sopenharmony_ci`scatter.R` tool. Even more useful it creates an actual scatter plot when using
3701cb0ef41Sopenharmony_cithe `--plot filename` option.
3711cb0ef41Sopenharmony_ci
3721cb0ef41Sopenharmony_ci```console
3731cb0ef41Sopenharmony_ci$ cat scatter.csv | Rscript benchmark/scatter.R --xaxis chunkLen --category encoding --plot scatter-plot.png --log
3741cb0ef41Sopenharmony_ci
3751cb0ef41Sopenharmony_ciaggregating variable: inLen
3761cb0ef41Sopenharmony_ci
3771cb0ef41Sopenharmony_cichunkLen     encoding      rate confidence.interval
3781cb0ef41Sopenharmony_ci      16        ascii 1515855.1           334492.68
3791cb0ef41Sopenharmony_ci      16 base64-ascii  403527.2            89677.70
3801cb0ef41Sopenharmony_ci      16  base64-utf8  322352.8            70792.93
3811cb0ef41Sopenharmony_ci      16      utf16le 1714567.5           388439.81
3821cb0ef41Sopenharmony_ci      16         utf8 1100181.6           254141.32
3831cb0ef41Sopenharmony_ci      64        ascii 3550402.0           661277.65
3841cb0ef41Sopenharmony_ci      64 base64-ascii 1093660.3           229976.34
3851cb0ef41Sopenharmony_ci      64  base64-utf8  997804.8           227238.04
3861cb0ef41Sopenharmony_ci      64      utf16le 3372234.0           647274.88
3871cb0ef41Sopenharmony_ci      64         utf8 1731941.2           360854.04
3881cb0ef41Sopenharmony_ci     256        ascii 5033793.9           723354.30
3891cb0ef41Sopenharmony_ci     256 base64-ascii 1447962.1           236625.96
3901cb0ef41Sopenharmony_ci     256  base64-utf8 1357269.2           231045.70
3911cb0ef41Sopenharmony_ci     256      utf16le 4039581.5           655483.16
3921cb0ef41Sopenharmony_ci     256         utf8 1828672.9           360311.55
3931cb0ef41Sopenharmony_ci    1024        ascii 5677592.7           624771.56
3941cb0ef41Sopenharmony_ci    1024 base64-ascii 1494171.7           227302.34
3951cb0ef41Sopenharmony_ci    1024  base64-utf8 1399218.9           224584.79
3961cb0ef41Sopenharmony_ci    1024      utf16le 4157452.0           630416.28
3971cb0ef41Sopenharmony_ci    1024         utf8 1824266.6           359628.52
3981cb0ef41Sopenharmony_ci```
3991cb0ef41Sopenharmony_ci
4001cb0ef41Sopenharmony_ciBecause the scatter plot can only show two variables (in this case _chunkLen_
4011cb0ef41Sopenharmony_ciand _encoding_) the rest is aggregated. Sometimes aggregating is a problem, this
4021cb0ef41Sopenharmony_cican be solved by filtering. This can be done while benchmarking using the
4031cb0ef41Sopenharmony_ci`--set` parameter (e.g. `--set encoding=ascii`) or by filtering results
4041cb0ef41Sopenharmony_ciafterwards using tools such as `sed` or `grep`. In the `sed` case be
4051cb0ef41Sopenharmony_cisure to keep the first line since that contains the header information.
4061cb0ef41Sopenharmony_ci
4071cb0ef41Sopenharmony_ci```console
4081cb0ef41Sopenharmony_ci$ cat scatter.csv | sed -E '1p;/([^,]+, ){3}128,/!d' | Rscript benchmark/scatter.R --xaxis chunkLen --category encoding --plot scatter-plot.png --log
4091cb0ef41Sopenharmony_ci
4101cb0ef41Sopenharmony_cichunkLen     encoding      rate confidence.interval
4111cb0ef41Sopenharmony_ci      16        ascii 1302078.5            71692.27
4121cb0ef41Sopenharmony_ci      16 base64-ascii  338669.1            15159.54
4131cb0ef41Sopenharmony_ci      16  base64-utf8  281904.2            20326.75
4141cb0ef41Sopenharmony_ci      16      utf16le 1381515.5            58533.61
4151cb0ef41Sopenharmony_ci      16         utf8  831183.2            33631.01
4161cb0ef41Sopenharmony_ci      64        ascii 4363402.8           224030.00
4171cb0ef41Sopenharmony_ci      64 base64-ascii 1036825.9            48644.72
4181cb0ef41Sopenharmony_ci      64  base64-utf8  780059.3            60994.98
4191cb0ef41Sopenharmony_ci      64      utf16le 3900749.5           158366.84
4201cb0ef41Sopenharmony_ci      64         utf8 1723710.6            80665.65
4211cb0ef41Sopenharmony_ci     256        ascii 8472896.1           511822.51
4221cb0ef41Sopenharmony_ci     256 base64-ascii 2215884.6           104347.53
4231cb0ef41Sopenharmony_ci     256  base64-utf8 1996230.3           131778.47
4241cb0ef41Sopenharmony_ci     256      utf16le 5824147.6           234550.82
4251cb0ef41Sopenharmony_ci     256         utf8 2019428.8           100913.36
4261cb0ef41Sopenharmony_ci    1024        ascii 8340189.4           598855.08
4271cb0ef41Sopenharmony_ci    1024 base64-ascii 2201316.2           111777.68
4281cb0ef41Sopenharmony_ci    1024  base64-utf8 2002272.9           128843.11
4291cb0ef41Sopenharmony_ci    1024      utf16le 5789281.7           240642.77
4301cb0ef41Sopenharmony_ci    1024         utf8 2025551.2            81770.69
4311cb0ef41Sopenharmony_ci```
4321cb0ef41Sopenharmony_ci
4331cb0ef41Sopenharmony_ci![compare tool boxplot](doc_img/scatter-plot.png)
4341cb0ef41Sopenharmony_ci
4351cb0ef41Sopenharmony_ci### Running benchmarks on the CI
4361cb0ef41Sopenharmony_ci
4371cb0ef41Sopenharmony_ciTo see the performance impact of a pull request by running benchmarks on
4381cb0ef41Sopenharmony_cithe CI, check out [How to: Running core benchmarks on Node.js CI][benchmark-ci].
4391cb0ef41Sopenharmony_ci
4401cb0ef41Sopenharmony_ci## Creating a benchmark
4411cb0ef41Sopenharmony_ci
4421cb0ef41Sopenharmony_ci### Basics of a benchmark
4431cb0ef41Sopenharmony_ci
4441cb0ef41Sopenharmony_ciAll benchmarks use the `require('../common.js')` module. This contains the
4451cb0ef41Sopenharmony_ci`createBenchmark(main, configs[, options])` method which will setup the
4461cb0ef41Sopenharmony_cibenchmark.
4471cb0ef41Sopenharmony_ci
4481cb0ef41Sopenharmony_ciThe arguments of `createBenchmark` are:
4491cb0ef41Sopenharmony_ci
4501cb0ef41Sopenharmony_ci* `main` {Function} The benchmark function,
4511cb0ef41Sopenharmony_ci  where the code running operations and controlling timers should go
4521cb0ef41Sopenharmony_ci* `configs` {Object} The benchmark parameters. `createBenchmark` will run all
4531cb0ef41Sopenharmony_ci  possible combinations of these parameters, unless specified otherwise.
4541cb0ef41Sopenharmony_ci  Each configuration is a property with an array of possible values.
4551cb0ef41Sopenharmony_ci  The configuration values can only be strings or numbers.
4561cb0ef41Sopenharmony_ci* `options` {Object} The benchmark options. Supported options:
4571cb0ef41Sopenharmony_ci  * `flags` {Array} Contains node-specific command line flags to pass to
4581cb0ef41Sopenharmony_ci    the child process.
4591cb0ef41Sopenharmony_ci  * `combinationFilter` {Function} Has a single parameter which is an object
4601cb0ef41Sopenharmony_ci    containing a combination of benchmark parameters. It should return `true`
4611cb0ef41Sopenharmony_ci    or `false` to indicate whether the combination should be included or not.
4621cb0ef41Sopenharmony_ci
4631cb0ef41Sopenharmony_ci`createBenchmark` returns a `bench` object, which is used for timing
4641cb0ef41Sopenharmony_cithe runtime of the benchmark. Run `bench.start()` after the initialization
4651cb0ef41Sopenharmony_ciand `bench.end(n)` when the benchmark is done. `n` is the number of operations
4661cb0ef41Sopenharmony_ciperformed in the benchmark.
4671cb0ef41Sopenharmony_ci
4681cb0ef41Sopenharmony_ciThe benchmark script will be run twice:
4691cb0ef41Sopenharmony_ci
4701cb0ef41Sopenharmony_ciThe first pass will configure the benchmark with the combination of
4711cb0ef41Sopenharmony_ciparameters specified in `configs`, and WILL NOT run the `main` function.
4721cb0ef41Sopenharmony_ciIn this pass, no flags except the ones directly passed via commands
4731cb0ef41Sopenharmony_ciwhen running the benchmarks will be used.
4741cb0ef41Sopenharmony_ci
4751cb0ef41Sopenharmony_ciIn the second pass, the `main` function will be run, and the process
4761cb0ef41Sopenharmony_ciwill be launched with:
4771cb0ef41Sopenharmony_ci
4781cb0ef41Sopenharmony_ci* The flags passed into `createBenchmark` (the third argument)
4791cb0ef41Sopenharmony_ci* The flags in the command passed when the benchmark was run
4801cb0ef41Sopenharmony_ci
4811cb0ef41Sopenharmony_ciBeware that any code outside the `main` function will be run twice
4821cb0ef41Sopenharmony_ciin different processes. This could be troublesome if the code
4831cb0ef41Sopenharmony_cioutside the `main` function has side effects. In general, prefer putting
4841cb0ef41Sopenharmony_cithe code inside the `main` function if it's more than just declaration.
4851cb0ef41Sopenharmony_ci
4861cb0ef41Sopenharmony_ci```js
4871cb0ef41Sopenharmony_ci'use strict';
4881cb0ef41Sopenharmony_ciconst common = require('../common.js');
4891cb0ef41Sopenharmony_ciconst { SlowBuffer } = require('node:buffer');
4901cb0ef41Sopenharmony_ci
4911cb0ef41Sopenharmony_ciconst configs = {
4921cb0ef41Sopenharmony_ci  // Number of operations, specified here so they show up in the report.
4931cb0ef41Sopenharmony_ci  // Most benchmarks just use one value for all runs.
4941cb0ef41Sopenharmony_ci  n: [1024],
4951cb0ef41Sopenharmony_ci  type: ['fast', 'slow'],  // Custom configurations
4961cb0ef41Sopenharmony_ci  size: [16, 128, 1024],  // Custom configurations
4971cb0ef41Sopenharmony_ci};
4981cb0ef41Sopenharmony_ci
4991cb0ef41Sopenharmony_ciconst options = {
5001cb0ef41Sopenharmony_ci  // Add --expose-internals in order to require internal modules in main
5011cb0ef41Sopenharmony_ci  flags: ['--zero-fill-buffers'],
5021cb0ef41Sopenharmony_ci};
5031cb0ef41Sopenharmony_ci
5041cb0ef41Sopenharmony_ci// `main` and `configs` are required, `options` is optional.
5051cb0ef41Sopenharmony_ciconst bench = common.createBenchmark(main, configs, options);
5061cb0ef41Sopenharmony_ci
5071cb0ef41Sopenharmony_ci// Any code outside main will be run twice,
5081cb0ef41Sopenharmony_ci// in different processes, with different command line arguments.
5091cb0ef41Sopenharmony_ci
5101cb0ef41Sopenharmony_cifunction main(conf) {
5111cb0ef41Sopenharmony_ci  // Only flags that have been passed to createBenchmark
5121cb0ef41Sopenharmony_ci  // earlier when main is run will be in effect.
5131cb0ef41Sopenharmony_ci  // In order to benchmark the internal modules, require them here. For example:
5141cb0ef41Sopenharmony_ci  // const URL = require('internal/url').URL
5151cb0ef41Sopenharmony_ci
5161cb0ef41Sopenharmony_ci  // Start the timer
5171cb0ef41Sopenharmony_ci  bench.start();
5181cb0ef41Sopenharmony_ci
5191cb0ef41Sopenharmony_ci  // Do operations here
5201cb0ef41Sopenharmony_ci  const BufferConstructor = conf.type === 'fast' ? Buffer : SlowBuffer;
5211cb0ef41Sopenharmony_ci
5221cb0ef41Sopenharmony_ci  for (let i = 0; i < conf.n; i++) {
5231cb0ef41Sopenharmony_ci    new BufferConstructor(conf.size);
5241cb0ef41Sopenharmony_ci  }
5251cb0ef41Sopenharmony_ci
5261cb0ef41Sopenharmony_ci  // End the timer, pass in the number of operations
5271cb0ef41Sopenharmony_ci  bench.end(conf.n);
5281cb0ef41Sopenharmony_ci}
5291cb0ef41Sopenharmony_ci```
5301cb0ef41Sopenharmony_ci
5311cb0ef41Sopenharmony_ci### Creating an HTTP benchmark
5321cb0ef41Sopenharmony_ci
5331cb0ef41Sopenharmony_ciThe `bench` object returned by `createBenchmark` implements
5341cb0ef41Sopenharmony_ci`http(options, callback)` method. It can be used to run external tool to
5351cb0ef41Sopenharmony_cibenchmark HTTP servers.
5361cb0ef41Sopenharmony_ci
5371cb0ef41Sopenharmony_ci```js
5381cb0ef41Sopenharmony_ci'use strict';
5391cb0ef41Sopenharmony_ci
5401cb0ef41Sopenharmony_ciconst common = require('../common.js');
5411cb0ef41Sopenharmony_ci
5421cb0ef41Sopenharmony_ciconst bench = common.createBenchmark(main, {
5431cb0ef41Sopenharmony_ci  kb: [64, 128, 256, 1024],
5441cb0ef41Sopenharmony_ci  connections: [100, 500],
5451cb0ef41Sopenharmony_ci  duration: 5,
5461cb0ef41Sopenharmony_ci});
5471cb0ef41Sopenharmony_ci
5481cb0ef41Sopenharmony_cifunction main(conf) {
5491cb0ef41Sopenharmony_ci  const http = require('node:http');
5501cb0ef41Sopenharmony_ci  const len = conf.kb * 1024;
5511cb0ef41Sopenharmony_ci  const chunk = Buffer.alloc(len, 'x');
5521cb0ef41Sopenharmony_ci  const server = http.createServer((req, res) => {
5531cb0ef41Sopenharmony_ci    res.end(chunk);
5541cb0ef41Sopenharmony_ci  });
5551cb0ef41Sopenharmony_ci
5561cb0ef41Sopenharmony_ci  server.listen(common.PORT, () => {
5571cb0ef41Sopenharmony_ci    bench.http({
5581cb0ef41Sopenharmony_ci      connections: conf.connections,
5591cb0ef41Sopenharmony_ci    }, () => {
5601cb0ef41Sopenharmony_ci      server.close();
5611cb0ef41Sopenharmony_ci    });
5621cb0ef41Sopenharmony_ci  });
5631cb0ef41Sopenharmony_ci}
5641cb0ef41Sopenharmony_ci```
5651cb0ef41Sopenharmony_ci
5661cb0ef41Sopenharmony_ciSupported options keys are:
5671cb0ef41Sopenharmony_ci
5681cb0ef41Sopenharmony_ci* `port` - defaults to `common.PORT`
5691cb0ef41Sopenharmony_ci* `path` - defaults to `/`
5701cb0ef41Sopenharmony_ci* `connections` - number of concurrent connections to use, defaults to 100
5711cb0ef41Sopenharmony_ci* `duration` - duration of the benchmark in seconds, defaults to 10
5721cb0ef41Sopenharmony_ci* `benchmarker` - benchmarker to use, defaults to the first available http
5731cb0ef41Sopenharmony_ci  benchmarker
5741cb0ef41Sopenharmony_ci
5751cb0ef41Sopenharmony_ci[autocannon]: https://github.com/mcollina/autocannon
5761cb0ef41Sopenharmony_ci[benchmark-ci]: https://github.com/nodejs/benchmarking/blob/HEAD/docs/core_benchmarks.md
5771cb0ef41Sopenharmony_ci[git-for-windows]: https://git-scm.com/download/win
5781cb0ef41Sopenharmony_ci[nghttp2.org]: https://nghttp2.org
5791cb0ef41Sopenharmony_ci[node-benchmark-compare]: https://github.com/targos/node-benchmark-compare
5801cb0ef41Sopenharmony_ci[t-test]: https://en.wikipedia.org/wiki/Student%27s_t-test#Equal_or_unequal_sample_sizes%2C_unequal_variances_%28sX1_%3E_2sX2_or_sX2_%3E_2sX1%29
5811cb0ef41Sopenharmony_ci[wrk]: https://github.com/wg/wrk
582