Files
2024-05-16 11:40:24 +02:00

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