【问题标题】:For-loop performance: storing array length in a variableFor-loop 性能:将数组长度存储在变量中
【发布时间】:2013-08-02 02:14:45
【问题描述】:

考虑同一个循环迭代的两个版本:

for (var i = 0; i < nodes.length; i++) {
    ...
}

var len = nodes.length;
for (var i = 0; i < len; i++) {
    ...
}

后一个版本比前一个版本快吗?

【问题讨论】:

标签: javascript performance for-loop


【解决方案1】:

我相信nodes.length 已经定义并且不会在每次使用时重新计算。所以第一个例子会更快,因为它定义了一个更少的变量。虽然差别不大。

【讨论】:

    【解决方案2】:

    更新:2015 年 12 月 16 日

    由于这个答案似乎仍然有很多观点,我想随着浏览器和 JS 引擎的不断发展重新审视这个问题。

    我没有使用 JSPerf,而是将 some code 放在一起,使用原始问题中提到的两种方法循环遍历数组。我已将代码放入函数中以分解功能,就像希望在现实世界的应用程序中完成:

    function getTestArray(numEntries) {
      var testArray = [];
      for (var i = 0; i < numEntries; i++) {
        testArray.push(Math.random());
      }
      return testArray;
    }
    
    function testInVariable(testArray) {
      for (var i = 0; i < testArray.length; i++) {
        doSomethingAwesome(testArray[i]);
      }
    }
    
    function testInLoop(testArray) {
      var len = testArray.length;
      for (var i = 0; i < len; i++) {
        doSomethingAwesome(testArray[i]);
      }
    }
    
    function doSomethingAwesome(i) {
      return i + 2;
    }
    
    function runAndAverageTest(testToRun, testArray, numTimesToRun) {
      var totalTime = 0;
      for (var i = 0; i < numTimesToRun; i++) {
        var start = new Date();
        testToRun(testArray);
        var end = new Date();
        totalTime += (end - start);
      }
      return totalTime / numTimesToRun;
    }
    
    function runTests() {
      var smallTestArray = getTestArray(10000);
      var largeTestArray = getTestArray(10000000);
    
      var smallTestInLoop = runAndAverageTest(testInLoop, smallTestArray, 5);
      var largeTestInLoop = runAndAverageTest(testInLoop, largeTestArray, 5);
      var smallTestVariable = runAndAverageTest(testInVariable, smallTestArray, 5);
      var largeTestVariable = runAndAverageTest(testInVariable, largeTestArray, 5);
    
      console.log("Length in for statement (small array): " + smallTestInLoop + "ms");
      console.log("Length in for statement (large array): " + largeTestInLoop + "ms");
      console.log("Length in variable (small array): " + smallTestVariable + "ms");
      console.log("Length in variable (large array): " + largeTestVariable + "ms");
    }
    
    console.log("Iteration 1");
    runTests();
    console.log("Iteration 2");
    runTests();
    console.log("Iteration 3");
    runTests();

    为了实现尽可能公平的测试,每个测试运行 5 次并取平均值。我还运行了整个测试,包括生成数组 3 次。在我的机器上对 Chrome 进行测试表明,使用每种方法所花费的时间几乎相同。

    请务必记住,此示例有点像玩具示例,事实上,大多数示例都从应用程序的上下文中提取出来,可能会产生不可靠的信息,因为您的代码正在执行的其他操作可能会直接影响性能或间接地。

    底线

    确定最适合您的应用程序的最佳方法是自己进行测试! JS 引擎、浏览器技术和 CPU 技术不断发展,因此您必须始终在应用程序的上下文中自行测试性能。同样值得问问自己是否有性能问题,如果没有,那么花时间进行用户无法察觉的微优化可能会更好地用于修复错误和添加功能,从而让用户更快乐:)。

    原答案:

    后一种会稍微快一些。 length 属性不会遍历数组来检查元素的数量,但每次在数组上调用它时,都必须取消引用该数组。通过将长度存储在变量中,每次循环迭代都不需要数组取消引用。

    如果您对在 javascript 中循环遍历数组的不同方式的性能感兴趣,请查看jsperf

    【讨论】:

    • 您的回答是最准确的。我认为差异并不显着,尤其是考虑到 chrome。
    • length 是一个属性,而不是一个函数。
    • 这个答案是错误的,或者至少不再正确,基于它的 jsperf。不过,意图是正确的。引用的 jsperf 使用了一个无关紧要的循环大小。对于 1000 次迭代,几乎没有差异。将循环提高到 100000 表示首先设置长度(而不是循环中的 arr.length)实际上慢了 33%,这也是错误的。请参阅@Esailija 的回答。
    • 我认为您已经颠倒了测试函数的名称。 testInLoop 实际上就是将长度缓存在一个变量中。
    • 有趣的是,如果runTests 在循环之前计算缓存变量测试,缓存长度的时间量会少两倍。但是,如果runTests 将首先在循环测试中计算,然后再进行缓存计算。时间安排将非常相似。在我的 Chromium 88 中。有什么想法吗?
    【解决方案3】:

    如果nodesDOM nodeList,那么第二个循环会快得多,因为在第一个循环中,您在每次迭代时查找 DOM(非常昂贵)。 jsperf

    【讨论】:

    【解决方案4】:

    公认的答案是不正确的,因为任何体面的引擎都应该能够hoist the property load out of the loop 使用如此简单的循环体。

    请参阅this jsperf - 至少在 V8 中 it is interesting to see 实际将其存储在变量中如何更改寄存器分配 - 在使用变量的代码中,sum 变量存储在堆栈中,而使用 array.length -in-a-loop-code 它存储在寄存器中。我认为 SpiderMonkey 和 Opera 也发生了类似的事情。

    根据作者JSPerf is used incorrectly 的说法,70% 的时间。此处所有答案中给出的这些损坏的 jsperfs 会产生误导性的结果,人们会从中得出错误的结论。

    一些危险信号是将代码而不是函数放入测试用例中,不测试结果的正确性或使用某种消除死代码消除的机制,在设置或测试用例中定义函数而不是全局。为了保持一致性,您需要在任何基准测试之前预热测试功能,这样编译就不会发生在计时部分。

    【讨论】:

    • 不是我的。我也讨厌那些反对票。好吧,您的回答似乎很有趣,我只是需要更多时间来深入探讨这个话题。谢谢。
    • 很多人忘记了编译器的用途,并且没有必要为了获得纳米级收益而对代码进行游戏,从长远来看甚至可能是有害的。
    • 有趣的是,使用接受的答案的 jsperf 和 100000 次迭代,它说(对我来说)首先设置长度会慢 33%(可靠)。如果我在定义之后的 prep 中添加了对 worker() 的调用,结果会翻转,将循环外的长度设置为快 33%。
    • 好吧,只要运行接受的答案,我的 Chromium 88 的缓存长度几乎快两倍
    【解决方案5】:

    根据w3schools "Reduce Activity in Loops",以下被认为是错误代码:

    for (i = 0; i < arr.length; i++) {
    

    以下被认为是好的代码:

    var arrLength = arr.length;
    for (i = 0; i < arrLength; i++) {
    

    由于访问 DOM 很慢,所以写了下面的代码来测试一下理论:

    <!doctype html>
    
    <html lang="en">
    	<head>
    		<meta charset="utf-8">
    		<title>my test scripts</title>
    	</head>
    	
    	<body>
    		<button onclick="initArray()">Init Large Array</button>
    		<button onclick="iterateArraySlowly()">Iterate Large Array Slowly</button>
    		<button onclick="iterateArrayQuickly()">Iterate Large Array Quickly</button>
    		
    		<p id="slow">Slow Time: </p>
    		<p id="fast">Fast Time: </p>
    		<p id="access"></p>
    
    
    	
    	<script>
    	var myArray = [];
    			
    		function initArray(){
    			var length = 1e6;
    			var i;
    			for(i = 0; i < length; i++) {
    				myArray[i] = i;
    			}
    			console.log("array size: " + myArray.length);
    		}
    		
    		function iterateArraySlowly() {
    			var t0 = new Date().getTime();
    			var slowText = "Slow Time: "
    			var i, t;
    			var elm = document.getElementById("slow");
    			for (i = 0; i < myArray.length; i++) {
    				document.getElementById("access").innerHTML = "Value: " + i;
    			}
    			t = new Date().getTime() - t0;
    			elm.innerHTML = slowText + t + "ms";
    		}
    		
    		function iterateArrayQuickly() {
    			var t0 = new Date().getTime();
    			var fastText = "Fast Time: "
    			var i, t;
    			var elm = document.getElementById("fast");
    			var length = myArray.length;
    			for (i = 0; i < length; i++) {
    				document.getElementById("access").innerHTML = "Value: " + i;
    			}
    			t = new Date().getTime() - t0;
    			elm.innerHTML = fastText + t + "ms";
    		
    		}
    	</script>
    	</body>
    </html>

    有趣的是,首先执行的迭代似乎总是胜过另一个。但是在每个被执行几次之后,被认为是“坏代码”的大部分时间似乎都赢了。也许比我更聪明的人可以解释原因。但就目前而言,语法方面我坚持使用对我来说更清晰的内容:

    for (i = 0; i < arr.length; i++) {
    

    【讨论】:

      【解决方案6】:

      在我使用过的任何基准测试中,这一直是性能最高的。

      for (i = 0, val; val = nodes[i]; i++) {
          doSomethingAwesome(val);
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-08-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-29
        相关资源
        最近更新 更多