Оптимизация JavaScript для циклов действительно необходима?

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

Итак, мы должны сделать следующее:

var names = ['George','Ringo','Paul','John'];
for(var i=0,j=names.length;i<j;i++){// Read array length once and assign it to a variable
    doSomeThingWith(names[i]);
}

вместо этого:

var names = ['George','Ringo','Paul','John'];
for(var i=0;i<names.length;i++){
    doSomeThingWith(names[i]);
}

Однако, я создал небольшой тестовый файл, чтобы сравнить два метода, но иногда первый случай был быстрее, а иногда и вторым.

Какую версию вы бы порекомендовали?

Ответ 1

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

Запустите собственный тест jsperf в любой версии IE. Там вы увидите постоянную разницу между этими двумя методами или многими другими старыми браузерами. Вы, по-видимому, только запускали его в Chrome, который настолько быстр и оптимизирован, что между этими двумя методами существует незначительная разница. В IE9 (который, скорее всего, лучше IE7 и IE8), метод, который предварительно кэширует длину, на 31% быстрее.

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

Это показывает разницу в браузерах, которые я пробовал, которые варьируются от почти никакой разницы до довольно значительной разницы в зависимости от браузера. В Chrome практически нет разницы. В IE9 сохранение первой длины почти на 50% быстрее.

Теперь, зависит ли эта разность скоростей к вашим сценариям от конкретного кода. Если у вас был огромный массив, который вы часто повторяли, в некоторых браузерах значительная разница в использовании этой формы:

for (var i = 0, len = list.length; i < len; i++) {
    // do code here
} 

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

На практике я склонен использовать короткую версию (меньше набрав), когда я не думаю, что мой раздел кода имеет критически высокую скорость и/или массив невелик, и я бы использовал более длинную версию, которая предварительно кэширует если я сознательно думаю о скорости или массив огромен, или я делаю много итераций по одному и тому же массиву.

Есть пара других причин программирования для предварительного кэширования длины. Если вы будете добавлять элементы в конец массива во время цикла, и вы не хотите, чтобы цикл повторялся по этим вновь добавленным элементам, вам НЕОБХОДИМО предварительно загрузить длину и только перебрать по исходно существующим элементам.

for (var i = 0, len = list.length; i < len; i++) {
    if (list[i] == "whatever") {
        list.push("something");
    }
} 

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

Ответ 2

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

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

Ответ 3

Я бы порекомендовал второе:

var names = ['George','Ringo','Paul','John'];
for (var i = 0; i < names.length; i++) {
  doSomeThingWith(names[i]);
}

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

Ответ 4

Как правило, кэширование "стоп-значения" цикла (в вашем случае names.length) является ценным, если оно является рассчитанным значением. Для рассматриваемого массива это просто поиск, поэтому вы получите мало, кэшируя его.

Ответ 5

Определить "really necessary".
Если вы зацикливаете массив из 4 элементов, я не думаю, что даже IE будет возражать, но имейте в виду, что вам, возможно, придется перебирать некоторые элементы dom; скажем, что у вас есть список (ul) с входами 1.000.000 (или больше) (li). Я думаю, что объявление дополнительной переменной спасет вас от проверки свойства length этого времени. Может быть, я немного преувеличиваю миллионную часть, но посмотрю результаты тестов только на 10000 li.
Оптимизированный цикл был почти в сто раз быстрее, чем "нормальный".

Мой вывод: оптимизируйте свои циклы... он не может навредить вам (или вашему коду или вашему браузеру).

Ответ 6

Я бы рекомендовал

var names = ['George','Ringo','Paul','John'];
var length = names.length;
for(var i=0;i<length;i++){
    doSomeThingWith(names[i]);
}

Ответ 7

2017 Обновленный ответ

Вы должны использовать оптимизированный/лучший способ.

В вашем точном примере: это так тривиально, что это не имеет значения. Даже при 50% -ной разнице в производительности, как указано в @jfriend00, это мало значит. Процессоры (включая текущие смартфоны) могут выполнять миллионы вычислений в секунду. Это означает, что доля миллисекунды просто не регистрируется пользователем, что соответствует тому, что размещено @Ned Batchelder.

Однако, кодирование не должно касаться того, с чем вам можно избавиться. Тем не менее, как сказал @DwB, "... стоп-значение... только ценно, если оно рассчитано". Имея это в виду, следующий код дает пример функции потери времени для возврата значения стопа. Здесь становится очевидным, насколько отличается скорость. Умножьте потенциальные недостатки на сервере, сложный клиентский код и другие интенсивные вычисления, и вы улучшите работу пользователей, используя лучшие практики.

		var eCount = document.getElementById("loopCount");
		var waitDiv = document.getElementById("waitDiv");
		var runButton = document.getElementById("runButton");
		var interCount = eCount.value.replace(/\D/g,'');
		var names = ['George','Ringo','Paul','John'];
		
		eCount.addEventListener("input", function(){			
			var value = parseInt(this.value.replace(/\D/g,'')).toLocaleString();
			this.value = value.toLocaleString();
		});


		function runLoop(){			
			interCount = eCount.value.replace(/\D/g,'');
			waitImg(true);
			setTimeout(function(){
				var cachedTime = loopTest("cached");
				var inlineTime = loopTest("inline");
				document.getElementById( "cached" ).innerText = cachedTime+" Cached";
				document.getElementById( "inline" ).innerText = inlineTime+" Not Cached";
				waitImg(false);
			}, 100); // delay to allow update of DOM with waitimg gif
			
		}
		
		function loopTest(meth){
			var worthlessVariable = 0;
			var t0 = performance.now();			
			if( meth == "cached" ){
				for( var i = 0, len = busyCalulations(); i < len; i++) {
					worthlessVariable = i;
				}
			}else{
				for( var i = 0; i < busyCalulations(); i++) {
					worthlessVariable = i;
				}
			}
			var t1 = performance.now();
			return (t1 - t0);
		}

		
		function busyCalulations(){
			// garbage math to simulate doing something
			// it returns interCount after some pointless math
			var limit = Math.floor(Math.random() * 20) + 20;
			return interCount*(limit*names.length)/(limit*names.length);
		}
		
		
		function waitImg(txt){ // display wait timer
			if (txt === true){
				waitDiv.style.visibility = "visible";
				runButton.style.visibility = "hidden";
			}else{
				waitDiv.style.visibility = "hidden";
				runButton.style.visibility = "visible";
			}
		}
	<h1>Loop Tester</h1>
<form onSubmit="return false;">
	Loop Length <input id="loopCount" type="text" value="100,000"><br>
	<br><br>
	<button id="runButton" onClick="runLoop();">Run Test</button>
	<div id="waitDiv" style="visibility: hidden"><img src="https://i.stack.imgur.com/5qXc3.gif"></div>
	<br><br>
	</form>
	<div><p>Times are in milliseconds</p>
		<div id="cached"></div>
		<div id="inline"></div>
	</div>

Ответ 8

Обратитесь к этой статье, в которой говорится об оптимизации JS-циклов.

Итак, простым решением для вас будет следующее:

let arr = ["a", "b", "c", "d", "e"]
let i = arr.length
while(i--) {
   callFn();
}

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

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

Потому что вышеприведенный код должен извлекать arr.length на каждой итерации.