对于循环性能:将数组长度存储在变量中

For-loop performance: storing array length in a variable

本文关键字:数组 存储 变量 于循环 循环 性能      更新时间:2023-09-26

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

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

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

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

公认的答案是不对的,因为任何像样的引擎都应该能够用如此简单的循环体将属性负载从循环中提升出来。

请参阅这个jsperf——至少在V8中,很有意思的是,在使用变量的代码中,sum变量存储在堆栈上,而使用循环中的array.length代码,它存储在寄存器中。我想类似的事情也发生在蜘蛛猴和歌剧中。

根据作者的说法,JSPerf被错误地使用了70%的时间。这里所有答案中给出的这些破碎的jsperf给出了误导性的结果,人们从中得出了错误的结论。

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

更新:2015年12月16日

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

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

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

根据w3schools"减少循环中的活动",以下代码被认为是错误代码:

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++) {

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

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

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

我相信nodes.length已经定义好了,不会在每次使用时重新计算。所以第一个例子会更快,因为它定义的变量少了一个。尽管这种差异并不明显。