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

时间:2013-08-01 08:49:33

标签: javascript performance for-loop

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

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

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

后一种版本无论如何比前者更快吗?

6 个答案:

答案 0 :(得分:33)

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

参见this jsperf - 至少在V8 it is interesting to see中实际存储它如何更改寄存器分配 - 在使用变量的代码中,sum变量存储在堆栈中而使用array.length - 在循环代码中,它存储在寄存器中。我假设SpiderMonkey和Opera也发生了类似的事情。

根据作者JSPerf is used incorrectly,70%的时间。在这里所有答案中给出的这些破碎的jsperf给出了误导性的结果,人们从中得出了错误的结论。

一些红色标志将代码放在测试用例中而不是函数中,不测试结果的正确性或使用某种消除死代码消除的机制,在设置或测试用例中定义函数而不是全局..为了保持一致性,你会想要在任何基准测试之前预热测试函数,以便在定时部分不会发生编译。

答案 1 :(得分:27)

更新: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");
}

runTests();
runTests();
runTests();

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

重要的是要记住,这个示例是一个玩具示例,事实上,从您的应用程序上下文中取出的大多数示例都可能产生不可靠的信息,因为您的代码正在执行的其他操作可能会直接影响性能或间接的。

底线

确定最适合您应用的方法的最佳方法是自行测试! JS引擎,浏览器技术和CPU技术不断发展,因此您必须始终在应用程序环境中自行测试性能。同样值得问自己是否存在性能问题,如果不这样做,那么花费时间进行用户难以察觉的微优化可以更好地修复错误和添加功能,从而使用户更加愉快:)。 p>

原始答案:

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

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

答案 2 :(得分:4)

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

答案 3 :(得分:2)

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

答案 4 :(得分:1)

我认为nodes.length已经定义,并且在每次使用时都没有重新计算。所以第一个例子会更快,因为它定义了一个较少的变量。虽然差异是不明显的。

答案 5 :(得分:0)

这一直是我使用过的所有基准测试中性能最高的。

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