Подтвердить что ты не робот

Производительность для цикла: сохранение длины массива в переменной

Рассмотрим две версии той же итерации цикла:

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

и

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

Является ли последняя версия в любом случае быстрее прежней?

4b9b3361

Ответ 1

Обновление: 16/12/2015

Поскольку этот ответ все еще, кажется, получает много просмотров, я хотел бы пересмотреть проблему, поскольку браузеры и 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");
}

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

Чтобы обеспечить как можно более справедливое испытание, каждый тест выполняется 5 раз и результаты усредняются. Я также проверил весь тест, включая генерацию массива 3 раза. Тестирование в Chrome на моей машине показало, что время, затраченное на использование каждого метода, было почти идентичным.

Важно помнить, что этот пример немного похож на игрушку, на самом деле большинство примеров, взятых из контекста вашего приложения, скорее всего, дадут недостоверную информацию, потому что другие вещи, которые делает ваш код, могут напрямую влиять на производительность или косвенно.

Нижняя строка

Лучший способ определить, что лучше всего подходит для вашего приложения, - проверить его самостоятельно! Двигатели JS, технология браузера и технология процессора постоянно развиваются, поэтому необходимо всегда проверять производительность для себя в контексте вашего приложения. Также стоит спросить себя, есть ли у вас проблема с производительностью вообще, если вы этого не сделаете, то время, потраченное на создание микро оптимизаций, которые незаметны для пользователя, может быть лучше потрачено на исправление ошибок и добавление функций, что приведет к более счастливым пользователям:).

Исходный ответ:

Последний будет немного быстрее. Свойство length не перебирает массив, чтобы проверять количество элементов, но каждый раз, когда он вызывается в массиве, этот массив должен быть разыменован. Сохраняя длину в переменной, разыгрывание массива не требуется каждой итерации цикла.

Если вы заинтересованы в производительности различных способов цикла через массив в javascript, посмотрите на это jsperf

Ответ 2

Принятый ответ неверен, потому что любой достойный движок должен вытащить загрузку свойств из цикла с помощью таких простых тел цикла.

Смотрите этот jsperf - по крайней мере, в V8 интересно посмотреть, как фактическое сохранение его в переменной меняет распределение регистров - в коде, где используется переменная, переменная sum хранится в стеке, тогда как с array.length -in-a-loop-code она сохраняется в реестре. Я предполагаю, что что-то подобное происходит и в SpiderMonkey и Opera.

По словам автора, JSPerf используется неправильно, 70% времени. Эти сломанные jsperfs, приведенные во всех ответах, приводят к ошибочным результатам, и люди делают неправильные выводы из них.

Некоторые красные флаги помещают код в тестовые примеры вместо функций, а не проверяют результат на правильность или используют какой-то механизм устранения устранения мертвого кода, определяя функцию в настройках или тестовых случаях вместо глобальных. Для согласованности вы захотите для прогрева тестовых функций перед любым эталоном, чтобы компиляция не выполнялась в разделе времени.

Ответ 3

if nodes is DOM nodeList, тогда второй цикл будет намного быстрее, потому что в первом цикле вы выполняете поиск DOM (очень дорогостоящий) на каждой итерации. jsperf

Ответ 4

Согласно 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>

Ответ 5

Я считаю, что nodes.length уже определен и не пересчитывается при каждом использовании. Таким образом, первый пример будет быстрее, потому что он определил одну менее переменную. Хотя разница была бы незаметной.