105 lines
3.0 KiB
JavaScript
105 lines
3.0 KiB
JavaScript
// Copyright <2024> Michael Fabian 'Xaymar' Dirks <info-at-xaymar-dot-com>
|
|
// Licensed under 3-Clause BSD License: https://opensource.org/license/bsd-3-clause
|
|
|
|
// Handle Node.JS
|
|
if (typeof window === 'undefined') {
|
|
import('node:perf_hooks').then((perfHooks) => {
|
|
global.performance = perfHooks.performance;
|
|
});
|
|
}
|
|
|
|
export default {};
|
|
export function benchTime(cycles, timeLimit, fnSetup, ...fnProcess) {
|
|
let finalResults = new Map();
|
|
|
|
function measureCycle(timeLimit, fn, args) {
|
|
let tmp = null;
|
|
let end, start = performance.now();
|
|
let iterations = 0;
|
|
|
|
// Run until we exceed the time limit for one cycle.
|
|
do {
|
|
tmp = fn.apply(null, args);
|
|
end = performance.now();
|
|
++iterations;
|
|
} while ((end - start) <= timeLimit);
|
|
tmp = undefined;
|
|
|
|
// Build a result object and return it.
|
|
return {
|
|
"iterations": iterations,
|
|
"start": start,
|
|
"end": end,
|
|
"duration": end - start,
|
|
"opsPerSec": (iterations / (end - start)) * 1000.0,
|
|
};
|
|
}
|
|
|
|
console.log(`Measuring ${fnProcess.length} functions...`);
|
|
let params = fnSetup();
|
|
//console.log("Setup function returned:", params);
|
|
|
|
// Perform this for every function passed.
|
|
for (let fn of fnProcess) {
|
|
let results = [];
|
|
//console.groupCollapsed(`${fn.name}: Running for ${cycles} cycles...`);
|
|
// Perform this N times.
|
|
for (let cycle = 0; cycle < cycles; cycle++) {
|
|
let result = {
|
|
"iterations": Number.NaN,
|
|
"start": Number.NaN,
|
|
"end": Number.NaN,
|
|
"duration": Number.NaN,
|
|
"opsPerSec": Number.NaN,
|
|
};
|
|
|
|
try {
|
|
result = measureCycle(timeLimit, fn, params);
|
|
results.push(result);
|
|
} catch (ex) {
|
|
console.error(`${fn.name}:`, ex);
|
|
break;
|
|
}
|
|
|
|
//console.log(`Cycle ${cycle}/${cycles}: ${result.iterations}, ${result.end - result.start}, ${result.opsPerSec} ops/s`);
|
|
}
|
|
|
|
// If we have more than 3 repeats, drop slowest and fastest as outliers.
|
|
if (results.length > 3) {
|
|
//console.log("Dropping slowest and fastest result.");
|
|
results = results.sort((a, b) => {
|
|
return (a.end - a.start) > (b.end - b.start);
|
|
}).slice(1);
|
|
results = results.sort((a, b) => {
|
|
return (a.end - a.start) < (b.end - b.start);
|
|
}).slice(1);
|
|
}
|
|
//console.groupEnd();
|
|
|
|
// Merge all results for the final average.
|
|
let iterations = 0;
|
|
let totalTime = 0;
|
|
let opsPerSecMin = +Infinity;
|
|
let opsPerSecMax = -Infinity;
|
|
let opsPerSec = 0;
|
|
for (let result of results) {
|
|
iterations += result.iterations;
|
|
totalTime += result.duration;
|
|
opsPerSec += result.opsPerSec;
|
|
if (opsPerSecMin > result.opsPerSec) {
|
|
opsPerSecMin = result.opsPerSec;
|
|
}
|
|
if (opsPerSecMax < result.opsPerSec) {
|
|
opsPerSecMax = result.opsPerSec;
|
|
}
|
|
}
|
|
let operations = opsPerSec / results.length; //iterations / totalTime;
|
|
let operationVariance = opsPerSecMax - opsPerSecMin;
|
|
console.log(`${fn.name}: ${(operations / 1000).toFixed(3)}±${(operationVariance / 1000).toFixed(3)} ops/ms, ${iterations} iterations over ${totalTime} ms.`);
|
|
|
|
finalResults.set(fn, results);
|
|
}
|
|
//console.log("Done.");
|
|
|
|
return finalResults;
|
|
} |