I wanted to compare execution time of my implementation of calculationg square root to native Math.sqrt() in Node.js.
So I made some functions to test it:
function getDuration(func, argumentGenerator, multiplier = 1, argumentOffset = 0) {
let args = []
for (let i = 0; i < multiplier; i++) {
args.push(argumentGenerator(i + argumentOffset))
}
let result = []
const start = performance.now()
for (let i = 0; i < multiplier; i++) {
result.push(func(args[i]))
}
const end = performance.now()
return {
time: end - start,
result: result,
}
}
function measureTime(func, repeat = 1, argumentGenerator, multiplier = 1) {
let result = []
for (let i = 0; i < repeat; i++) {
result.push(
getDuration(func, argumentGenerator, multiplier, i * multiplier)
);
}
return result
}
and the test subjects:
const indexArg = (i) => i * i + 1;
let functionsToTest = [
[
function BOTH(x) {
return Math.sqrt(x) - NewtonSquareRoot(x)
},
1e3, indexArg, 1e4
],
[
NewtonSquareRoot,
1e3, indexArg, 1e4
],
[
Math.sqrt,
1e3, indexArg, 1e4
],
];
let results = {}
for (const fArg of functionsToTest) {
let result = measureTime(...fArg)
results[fArg[0].name] = MMMM(result.map(x => x.time))
}
console.table(results);
And the result is:
┌──────────────────┬────────┬────────┬────────┬────────┐
│ (index) │ min │ max │ mean │ median │
├──────────────────┼────────┼────────┼────────┼────────┤
│ BOTH │ 0.9142 │ 3.8853 │ 1.3225 │ 1.2812 │
│ NewtonSquareRoot │ 0.9435 │ 2.515 │ 1.6164 │ 1.6612 │
│ sqrt │ 0.1026 │ 0.9474 │ 0.1225 │ 0.107 │
└──────────────────┴────────┴────────┴────────┴────────┘
I would expect the "BOTH" function mean execution time to be closer to sum of 2 others, but it's even lower than execution of my implementation alone.
I noticed that rearrangement of functions results in lower time for the first function:
┌──────────────────┬────────┬────────┬────────┬────────┐
│ (index) │ min │ max │ mean │ median │
├──────────────────┼────────┼────────┼────────┼────────┤
│ NewtonSquareRoot │ 0.8823 │ 3.3975 │ 1.2753 │ 1.2644 │
│ sqrt │ 0.1025 │ 0.8317 │ 0.1325 │ 0.1067 │
│ BOTH │ 1.1295 │ 2.443 │ 1.55 │ 1.541 │
└──────────────────┴────────┴────────┴────────┴────────┘
┌──────────────────┬────────┬────────┬────────┬────────┐
│ (index) │ min │ max │ mean │ median │
├──────────────────┼────────┼────────┼────────┼────────┤
│ sqrt │ 0.0294 │ 1.7835 │ 0.062 │ 0.0329 │
│ NewtonSquareRoot │ 0.9475 │ 3.354 │ 1.6375 │ 1.6593 │
│ BOTH │ 1.1293 │ 2.4975 │ 1.5394 │ 1.5409 │
└──────────────────┴────────┴────────┴────────┴────────┘
("BOTH" is still lower in the last result for some reason) But it seems to me that if the function isn't first, the results are consistent.
Why does that happen? How can I make it consistent for 1st function also?
I was thinking about adding dummy to skip the first execution
measureTime((x) => x, 1, (i) => i, 32);
the results are consistent, but it doesn't look like an elegant solution.
This works only if the dummy repetition count is 32 or higher and I am lost.
result.pusharray operation, which accesses memory and can lead to a garbage collection. This may falsify the results.sqrtfunction as well