I've tried searching questions to find an answer to this, but came up short. I'll admit that I may not be using the correct language to define what I'm looking to answer, but I'm going to try my best to illustrate it here as clearly as possible.
I'm involved in a large project which deals with very small arrays of data that get iterated over tens (and sometimes hundreds) of millions of times. One of the functions in this project adds the values from one array to the values of another array at the same index. This function is a bottleneck and consistently adds several seconds to the processing. I was tasked refactoring the function if possible to try and squeeze a little more speed from the operation.
TLDR;
Why is accessing individual array indices (indexes?) faster than accessing them from within a forEach
loop? And for bonus points, why on earth does the FIRST iteration of the original function run so slow, while the other 9 iterations of that function take less than half a second?
Since the arrays are "small", the original function does the math in a very direct way
a[0] = a[0] + b[0];
a[1] = a[1] + b[1];
a[2] = a[2] + b[2];
etc...
I thought maybe it would be faster to just loop through the array using
a.forEach((num, i) => {
a[i] = a[i] + b[i];
});
Surprisingly to me, this is not only slower, but SIGNIFICANTLY slower. I set up a test function to iterate both functions a set number of times and output the time it took per x iterations. Turns out, the original function takes about 2 seconds the first round, but each round after that only takes about 0.3 seconds, whereas the forEach
version of the function consistently takes about 1.3 seconds per round.
I create a pen with the test so that you can play with the variables and see the results for yourself, but here's the exact code of each function, along with the results from the tests.
// Original function
var addArrays1 = function(a1, a2) {
if ([a1.constructor, a2.constructor].includes(Array) && (a1.length === a2.length)) {
a1[0] += a2[0];
a1[1] += a2[1];
a1[2] += a2[2];
a1[3] += a2[3];
a1[4] += a2[4];
a1[5] += a2[5];
return a1;
}
return false;
};
// "optimized" function?
var addArrays2 = function(a1, a2) {
if ([a1.constructor, a2.constructor].includes(Array) && (a1.length === a2.length)) {
a1.forEach((num, i) => {
a1[i] += a2[i];
});
return a1;
}
return false;
};
And here are the results from those tests which include a sanity check just to make sure both functions are returning the same results.
{
"function": {
"addArrays1": {
"Round 0": {
"time": "3.10 seconds"
},
"Round 1": {
"time": "0.17 seconds"
},
"Round 2": {
"time": "0.20 seconds"
},
"Round 3": {
"time": "0.19 seconds"
},
"Round 4": {
"time": "0.18 seconds"
},
"Round 5": {
"time": "0.18 seconds"
},
"Round 6": {
"time": "0.19 seconds"
},
"Round 7": {
"time": "0.18 seconds"
},
"Round 8": {
"time": "0.19 seconds"
},
"Round 9": {
"time": "0.19 seconds"
},
"averageTime": "0.48 seconds",
"totalTime": "4.77 seconds",
"sanityCheck": {
"var1": [
48000000.54457935,
40000000.80781116,
170000000.30154032,
64000000.094739415,
62000001.09541846,
46000000.759361744
],
"var2": [
0.24,
0.2,
0.85,
0.32,
0.31,
0.23
]
}
},
"addArrays2": {
"Round 0": {
"time": "1.27 seconds"
},
"Round 1": {
"time": "1.23 seconds"
},
"Round 2": {
"time": "1.21 seconds"
},
"Round 3": {
"time": "1.21 seconds"
},
"Round 4": {
"time": "1.22 seconds"
},
"Round 5": {
"time": "1.20 seconds"
},
"Round 6": {
"time": "1.20 seconds"
},
"Round 7": {
"time": "1.20 seconds"
},
"Round 8": {
"time": "1.20 seconds"
},
"Round 9": {
"time": "1.21 seconds"
},
"averageTime": "1.21 seconds",
"totalTime": "12.14 seconds",
"sanityCheck": {
"var1": [
48000000.54457935,
40000000.80781116,
170000000.30154032,
64000000.094739415,
62000001.09541846,
46000000.759361744
],
"var2": [
0.24,
0.2,
0.85,
0.32,
0.31,
0.23
]
}
}
}
}
If you'd like to review my test methodology, you can see it here: https://codepen.io/spamgoblin/pen/xxRBjoj?editors=0010 However, beware that it is set to run 10 rounds (to get the average) of 10,000,000 iterations per function so it will take a little while to load.