Действительно ли необходима оптимизация JavaScript для циклов? - PullRequest
17 голосов
/ 07 августа 2011

Я прочитал, что рекомендуется оптимизировать циклы в 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]);
}

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

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

Ответы [ 8 ]

22 голосов
/ 07 августа 2011

Во-первых, я должен сказать, что этот ответ написан в 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 году, может быть в будущем встроена в более современный браузер, поэтому оптимизация с ручным кодом больше не нужна. Поэтому, если вы пытаетесь оптимизировать что-то для сегодняшней производительности, вам нужно протестировать в современных браузерах, вы не можете просто полагаться на прочитанные вами вещи, которым может быть несколько лет.

13 голосов
/ 07 августа 2011

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

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

5 голосов
/ 07 августа 2011

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

5 голосов
/ 07 августа 2011

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

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

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

4 голосов
/ 07 августа 2011

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

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

3 голосов
/ 07 августа 2011

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

var names = ['George','Ringo','Paul','John'];
var length = names.length;
for(var i=0;i<length;i++){
    doSomeThingWith(names[i]);
}
1 голос
/ 22 апреля 2017

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>
0 голосов
/ 15 марта 2018

Пожалуйста, обратитесь к этому сообщению, в котором говорится об оптимизации 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 на каждой итерации.

...