|
// 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; |