为什么Array.prototype.fill()与`for`循环相比具有如此大的性能差异?

moo*_*ard 7 javascript arrays google-chrome v8

在对该Array.prototype.fill()方法进行一些测试(Chrome on macOS)时,它显然比简单地创建自己的for循环和填充数组慢两倍(如果不是更慢).

显然做类似的事情:

for( var i = 0; i < Array.length; i++) {
   A[i] = 0;
}
Run Code Online (Sandbox Code Playgroud)

VS

Array.fill(0);
Run Code Online (Sandbox Code Playgroud)

Array.fill()方法需要大约210-250ms来填充大小为10000000的数组,而for循环需要大约70-90ms.似乎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);
Run Code Online (Sandbox Code Playgroud)

与我在本地测试时相比,上面实际上显示出更大的差异.

编辑:我现在意识到,经过进一步的测试,当切换到Firefox及其真正的引擎依赖时,差异会大大减少.我猜这主要是因为不同的JavaScript引擎优化循环与方法的结果.尽管如此,似乎仍然Array.prototype.fill()可以优化其中的循环以解决这种差异.

tra*_*r53 5

结果与报告一致,即 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>
Run Code Online (Sandbox Code Playgroud)

可以调整阵列大小以适应所用设备的性能。

Windows 下 Chrome 的结果显示,对于 test 的前两次测试点击,循环的性能取得了巨大的进步。第二次点击时,循环的时间似乎有所改善。第三次单击时,循环和填充方法似乎都得到了优化,并且以几乎相同且改进的速度运行。重新加载页面后结果是可重复的。

Array.prototype.fill我发现这与 Chrome 脚本优化策略一致,但与用 C++ 或类似语言编写的Chrome 不一致。尽管 Array.prototype.fill.toString()将函数体报告为“本机代码”,但并未说明它是用什么语言编写的。


更新

添加了自定义填充方法的计时,为速度而编写,并存储为Array.prototype.customFill.

Array.prototype.fillFirefox 的计时与脚本编写的一致。本机实现的性能优于循环,并且通常(但并非总是)比自定义填充方法更快。

Array.prototype.fillChrome 显示的时间也与用某种优化的脚本编写的时间一致。测试的所有三种填充方法都显示,在一两次测试点击后速度有所提高。

然而,自定义填充方法的启动速度比 Chrome 原生版本快十倍以上。您需要在自定义方法中放入无意义的代码,以使其减慢到足以接近本机方法的初始速度。相反,优化后,本机方法的速度大约是原来的两倍 - 用 Ja​​vaScript 编写的自定义方法永远不会得到相同程度的优化。

虽然 Chrome 的Array.prototype.fill方法可以用 JavaScript 编写,但似乎需要额外的解释来解释最初的缓慢和最终的性能优化。