Оптимизация 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
на каждой итерации.