Skip to content

Instantly share code, notes, and snippets.

@Gmousse
Last active June 9, 2019 14:09
Show Gist options
  • Save Gmousse/131f8013582c6254c5c38e5d0a4156b4 to your computer and use it in GitHub Desktop.
Save Gmousse/131f8013582c6254c5c38e5d0a4156b4 to your computer and use it in GitHub Desktop.
Simple JS functions benchmark

Simple Benchmark

A simple benchmark library to benchmark js functions without installing 1go of depedencies.

Requirements

Nodejs: 6.x.x, 7.x.x, 8.x.x, 9.x.x

Installation

With npm: npm install --save-dev gist:131f8013582c6254c5c38e5d0a4156b4

With git: git clone https://gist.github.com/131f8013582c6254c5c38e5d0a4156b4.git

Usage

Import the library main class: const Benchmark = require('simple-benchmark');

Create multiple benchmark instances:

const stringConcat = new Benchmark('String concat', 10)  // Create a 'String concat report' with 10 repeats.
    .addFunction('With +', () => string1 + ' ' + string2)  // Add a new function to test.
    .addFunction('With format', () => `${string1} ${string2}`)  // Add a new function to test.
    .computeReport(); // Generate the report

console.log(stringConcat);

Example of report:

Report "String concat"

Execution time by function:
  - "With +":
        mean: 0.0021 ms
        std deviation: 0.0053 ms (252.38% of variation)
  - "With format":
        mean: 0.0022 ms
        std deviation: 0.0052 ms (236.35999999999999% of variation)

Faster function: "With +" with 0.0021 ms
   - 1.0476 times faster than "With format" (4.55 % of gain)
-------------------

You can also pipe the standard output in a file: node mybench.js >> report.txt

// Copyright (c) 2018 Guillaume Mousnier
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
class Benchmark {
constructor(name, repeats = 10) {
/**
* A simple class used to compare different functions.
* @param {String} name The name of the report.
* @param {Number} [repeats=10] Number of repetitions used by function.
*/
this.funcs = [];
this.name = name;
this.repeats = repeats;
}
_computeExecutionTimeSerie(func) {
return Array.from(Array(this.repeats)).map(() => {
const timer = process.hrtime();
func();
const diff = process.hrtime(timer);
return diff[1] / 1e6;
});
}
_round(number) {
return Math.round((number * 10000), 2) / 10000;
}
_computeMean(serie) {
return serie.reduce((p, n) => p + n, 0) / this.repeats;
}
_computeStandardDeviation(serie, mean) {
return Math.sqrt(Number(serie.reduce((p, n) => p + Math.pow(n - mean, 2), 0)) / this.repeats);
}
_computeVariationCoefficient(mean, stdev) {
return stdev / mean;
}
_computeResults() {
return this.funcs.map(
({name, func}) => {
const serie = this._computeExecutionTimeSerie(func);
const mean = this._round(this._computeMean(serie));
const stdev = this._round(this._computeStandardDeviation(serie, mean));
const varCoefficient = this._round(this._computeVariationCoefficient(mean, stdev));
return {name, mean, stdev, varCoefficient};
}
);
}
_analyzeBestResult(results) {
const bestResult = results.reduce((p, n) => p.mean > n.mean ? n : p);
const details = results
.filter(result => !(result.name === bestResult.name && result.time === bestResult.time))
.map(result => ({
...result,
timesFaster: this._round(result.mean / bestResult.mean),
gainCoefficient: this._round((result.mean - bestResult.mean) / result.mean)}
));
return {...bestResult, details};
}
_formatReport(results, bestResult) {
return `
Report "${this.name}"
Execution time by function:
${results.map(({name, mean, stdev, varCoefficient}) => (
` - "${name}":
mean: ${mean} ms
std deviation: ${stdev} ms (${varCoefficient * 100}% of variation)`)).join('\n')}
Faster function: "${bestResult.name}" with ${bestResult.mean} ms
${bestResult.details.map(({name, gainCoefficient, timesFaster}) => (
` - ${timesFaster} times faster than "${name}" (${gainCoefficient * 100} % of gain)`
)).join('\n')}
-------------------
`;
}
addFunction(name, func) {
/**
* Add a new function to this benchmark.
* @param {String} name The name of the function used in the report.
* @param {Function} func The function to measure.
*/
this.funcs = this.funcs.concat([{name, func}]);
return this;
}
computeReport() {
/**
* Compute results and write a string report.
* @returns {String} The report as a string.
*/
const results = this._computeResults();
const bestResult = this._analyzeBestResult(results);
return this._formatReport(results, bestResult);
}
}
module.exports = Benchmark;
{
"name": "simple-benchmark",
"version": "0.1.0",
"main": "./benchmark.js"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment