Chrome 为什么Array.prototype.fill()与for循环相比有这么大的性能差异?

gev0vcfq  于 2023-05-27  发布在  Go
关注(0)|答案(4)|浏览(136)

在对Array.prototype.fill()方法进行一个小测试(macOS上的Chrome)时,它显然比简单地创建自己的for循环并填充数组慢了近两倍(如果不是更慢的话)。
明确地做类似于:

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

vs

Array.fill(0);

Array.fill()方法将花费大约210- 250 ms来填充大小为10000000的数组,而for循环将花费大约70- 90 ms。看起来Array.fill()方法可以重写为简单地使用直接循环,因为您总是知道初始索引和目标索引。

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

与我在本地测试时相比,上述结果实际上显示了更大的差异。
编辑:我现在意识到,经过进一步的测试,差异减少了很多切换到Firefox和它的真正引擎依赖。我猜这主要是不同JavaScript引擎如何优化循环和方法的结果。不过,似乎仍然可以优化Array.prototype.fill()中的循环来解决这个差异。

n3schb8v

n3schb8v1#

结果与Chrome的部分内容是用JavaScript编写的报告一致,并依赖于运行时分析和优化来提高性能。
我将测试代码打包在一个函数中,可以从一个测试页面中重复调用,该页面可以加载到不同的浏览器中(这不是一个可运行的片段):

<!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>

可以调整阵列大小以适应所使用的设备的性能。
Windows下Chrome的结果显示,循环的性能大幅提升 *,前两次测试点击测试 *。在第二次点击时,循环的时间似乎有所改善。在第三次点击时,循环和填充方法似乎都得到了优化,并且以几乎相同的速度运行,并且速度有所提高。重新加载页面后,结果可重复。
我发现这与Chrome的脚本优化策略一致,与Chrome的Array.prototype.fill是用C++或类似语言编写的不一致。尽管Array.prototype.fill.toString()将函数体报告为“本机代码”,但它并没有说明它是用什么语言编写的。

  • 更新 *

添加了自定义填充方法的计时,为提高速度而编写,并存储为Array.prototype.customFill
Firefox的计时与脚本中编写的Array.prototype.fill一致。本机实现优于循环,并且通常(但不总是)比自定义填充方法快。
Chrome显示的时间也与Array.prototype.fill一致,它是以某种脚本编写的,并且经过了优化。测试的所有三种填充方法在一次或两次测试点击后显示速度增加。
然而,自定义填充方法的启动速度比Chromes本机版本快十倍以上。您需要在自定义方法中放入无意义的代码,以使其足够慢,以接近本机方法的初始速度。相反,在优化之后,本地方法的速度大约是原来的两倍--用JavaScript编写的自定义方法永远不会得到相同程度的优化。
虽然Chromes Array.prototype.fill方法可以用JavaScript编写,但似乎需要额外的解释来解释最初的缓慢和最终的性能优化。

fafcakar

fafcakar2#

这个JSPerf确认fill比for循环慢。

kkih6yb8

kkih6yb83#

当数组大小为1e5时,fill总是获胜。当数组大小为1e7时,for获胜

tpxzln5u

tpxzln5u4#

我已经在jsbench.mejsperf.app中测试过了,因为jsperf.com没有停产。
这两个网站都显示Array.fill是最快的方法。
参考链接:

相关问题