Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Array.prototype.fill() have such a large performance difference compared to a `for` loop?

In doing a little testing (Chrome on macOS) of the Array.prototype.fill() method, its clearly almost twice as slow (if not slower) than simply creating your own for loop and filling your array.

Clearly doing something like:

for( var i = 0; i < Array.length; i++) {
   A[i] = 0;
}

vs

Array.fill(0);

The Array.fill() method would take ~210-250ms to fill an array of size 10000000, while the for loop would take ~70-90ms. It just seems that the Array.fill() method could be re-written to simply use a straight forward loop since you'd always know your initial index and target index.

let arrayTest = new Array(10000000),
    startTime,
    endTime;

startTime = performance.now();
arrayTest.fill(0);
endTime = performance.now();

console.log("%sms", endTime - startTime);
arrayTest = new Array(10000000);
startTime = performance.now();
for (let i = 0; i < arrayTest.length; i++){
  arrayTest[i] = 0;
}
endTime = performance.now();

console.log("%sms", endTime - startTime);

The above actually shows an even greater discrepancy compared to when I tested locally.

Edit: I realize now after further testing that the discrepancies are diminished a lot when switching over to Firefox and its really engine dependent. I am guessing this is mainly a result of how different JavaScript engines are optimizing loops vs a method. It still seems as though a loop within the Array.prototype.fill() could be optimized to resolve this difference though.

like image 313
mootrichard Avatar asked Feb 17 '18 00:02

mootrichard


2 Answers

The result is consistent with reports that parts of Chrome are written in JavaScript, and rely on run time profiling and optimization to improve performance.

I packaged the test code in a function to be called repeatedly from a test page which can be loaded into different browsers (this is not a runnable snippet):

<!DOCTYPE html>
<html><head><meta charset="utf-8">
<title>Array.prototype.fill</title>
<script>

Array.prototype.customFill = function( value, start = 0, end = this.length) {
    var count = end-start;
    if( count > 0 && count === Math.floor(count)){
        while( count--)
            this[start++]=value;
    }
    return this;
}

function test() {  
    let arrayTest,
        startTime,
        endTime,
        arraySize = 1000000;

    arrayTest = new Array(arraySize);
    startTime = performance.now();
    for (let i = 0; i < arrayTest.length; i++){
      arrayTest[i] = 0;
    }
    endTime = performance.now();
    console.log("%sms (loop)", endTime - startTime);

    arrayTest = new Array(arraySize);
    startTime = performance.now();
    arrayTest.fill(0);
    endTime = performance.now();
    console.log("%sms (fill)", endTime - startTime);

    arrayTest = new Array(arraySize);
    startTime = performance.now();
    arrayTest.customFill(0);
    endTime = performance.now();
    console.log("%sms (custom fill)", endTime - startTime);   
}
</script>
</head>
<body>
    open the console and click <button type="button" onclick="test()">test</button>
</body>
</html>

The array size can be adjusted to suit the performance of the device used.

The results for Chrome under Windows showed a large performance win for the loop, for the first two test clicks on test. On the second click, timing for the loop seemed to improve. On the third click both loop and fill methods appeared to be optimized and ran with nearly equal, and improved, speed. Results were repeatable after reloading the page.

I find this consistent with Chrome script optimization strategies and inconsistent with Chrome's Array.prototype.fill being written in C++ or similar. Although Array.prototype.fill.toString() reports the function body as "native code" is doesn't say what language it is written in.


Update

Added timings for a custom fill method, written for speed, and stored as Array.prototype.customFill.

Timings for Firefox are consistent with Array.prototype.fill being written in script. The native implementation outperformed the loop and was generally (but not always) faster than the custom fill method.

Timings for Chrome show are also consistent with Array.prototype.fill being written in some kind of script that becomes optimized. All three fill methods tested shown an increase in speed after one or two test clicks.

However, the custom fill method starts out over ten times faster than Chromes native version. You would need to put nonsense code in the custom method to slow it down enough to approach the native method's initial speed. Conversely, after optimization, the native method is around twice as fast - the custom method written in JavaScript never gets optimized to the same extent.

While Chromes Array.prototype.fill method could be written in JavaScript, additional explanation seems to be needed to account for the initial slowness and final performance optimizations noted.

like image 131
traktor Avatar answered Oct 22 '22 13:10

traktor


This JSPerf confirms that fill is slower than a for loop.

like image 25
brendo Avatar answered Oct 22 '22 13:10

brendo