Циклы действительно быстрее наоборот? - PullRequest
252 голосов
/ 27 августа 2009

Я слышал это довольно много раз. Действительно ли циклы JavaScript действительно быстрее при обратном отсчете? Если так, то почему? Я видел несколько примеров набора тестов, показывающих, что обратные циклы быстрее, но я не могу найти объяснения, почему!

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

т.е.

for (var i = count - 1; i >= 0; i--)
{
  // count is only evaluated once and then the comparison is always on 0.
}

Ответы [ 34 ]

877 голосов
/ 30 октября 2012

Это не значит, что i-- быстрее, чем i++. На самом деле, они оба одинаково быстры.

В восходящих циклах требуется время для оценки i размера вашего массива. В этом цикле:

for(var i = array.length; i--;)

Вы оцениваете .length только один раз, когда объявляете i, тогда как для этого цикла

for(var i = 1; i <= array.length; i++)

вы оцениваете .length каждый раз, когда вы увеличиваете i, когда вы проверяете i <= array.length.

В большинстве случаев вам даже не стоит беспокоиться об этом виде оптимизации .

193 голосов
/ 27 августа 2009

Этот парень сравнивал множество циклов в javascript, во многих браузерах. У него также есть набор тестов , так что вы можете запустить их самостоятельно.

Во всех случаях (если я не пропустил ни одного в моем чтении) самый быстрый цикл был:

var i = arr.length; //or 10
while(i--)
{
  //...
}
126 голосов
/ 30 октября 2012

Я пытаюсь дать широкую картину с этим ответом.

Следующие мысли в скобках были моей верой, пока я только недавно не проверил проблему:

[[С точки зрения языков низкого уровня , таких как C / C ++ , код скомпилирован так, что процессор имеет специальную команду условного перехода, когда переменная равна нулю (или не равна нулю).
Кроме того, если вам небезразлична такая большая оптимизация, вы можете использовать ++i вместо i++, поскольку ++i - это однопроцессорная команда, тогда как i++ означает j=i+1, i=j.]]

Действительно быстрые циклы можно сделать, развернув их:

for(i=800000;i>0;--i)
    do_it(i);

Это может быть намного медленнее, чем

for(i=800000;i>0;i-=8)
{
    do_it(i); do_it(i-1); do_it(i-2); ... do_it(i-7);
}

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

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

Следовательно, в JavaScript я бы предложил использовать что-то вроде

array.forEach(function(i) {
    do_it(i);
});

Он также менее подвержен ошибкам, и браузеры имеют возможность оптимизировать ваш код.

[ЗАМЕЧАНИЕ: не только браузеры, но и пространство для оптимизации можно просто переопределить, просто переопределите функцию forEach (в зависимости от браузера), чтобы она использовала новейшие хитрости! :) @ A.M.K. говорит, что в особых случаях стоит использовать array.pop или array.shift. Если вы сделаете это, положите его за занавес. максимальное превышение заключается в добавлении опций к forEach для выбора алгоритма зацикливания.]

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

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

Я провел более тщательную проверку, и оказалось, что в C / C ++, даже для операций 5e9 = (50 000 x 100 000), нет никакой разницы между повышением и уменьшением , если тестирование выполняется с константой, как говорит @alestanis. (Результаты JsPerf иногда противоречивы, но по большому счету говорят одно и то же: большой разницы не бывает.)
Так что --i оказывается довольно «шикарной» вещью. Это только делает вас похожим на лучшего программиста. :)

С другой стороны, для развёртывания в этой ситуации 5e9 это снизило меня с 12 секунд до 2,5 секунд, когда я шел на 10 секунд, и до 2,1 секунд, когда я шел на 20 секунд. Это было без оптимизации, а оптимизация привела к невообразимому небольшому времени. :) (Развертывание может быть сделано моим способом выше или с использованием i++, но это не продвигает вещи в JavaScript.)

В целом: сохраняйте i-- / i++ и ++i / i++ различия с собеседованиями при работе, придерживайтесь array.forEach или других сложных библиотечных функций, если они доступны. ;)

33 голосов
/ 30 октября 2012

i-- так же быстро, как i++

Этот код ниже, чем ваш, но использует дополнительную переменную:

var up = Things.length;
for (var i = 0; i < up; i++) {
    Things[i]
};

Рекомендуется НЕ оценивать размер массива каждый раз. Для больших массивов наблюдается снижение производительности.

21 голосов
/ 30 октября 2012

Поскольку вас интересует эта тема, взгляните на статью в блоге Грега Реймера о тесте цикла JavaScript, Какой самый быстрый способ кодирования цикла в JavaScript? :

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

Вы также можете выполнить тест производительности в цикле , открыв https://blogs.oracle.com/greimer/resource/loop-test.html (не работает, если JavaScript заблокирован в браузере, например, NoScript ).

EDIT:

Более поздний тест, созданный Миланом Адамовским, можно выполнить во время выполнения здесь для разных браузеров.

Для Тестирование в Firefox 17.0 на Mac OS X 10.6 Я получил следующий цикл:

var i, result = 0;
for (i = steps - 1; i; i--) {
  result += i;
}

как самый быстрый предшествует:

var result = 0;
for (var i = steps - 1; i >= 0; i--) {
  result += i;
}

Benchmark example.

18 голосов
/ 30 октября 2012

Это не -- или ++, это операция сравнения. С -- вы можете использовать сравнение с 0, а с ++ вам нужно сравнить его с длиной. На процессоре сравнение с нулем обычно доступно, тогда как сравнение с конечным целым числом требует вычитания.

a++ < length

фактически компилируется как

a++
test (a-length)

Таким образом, процессору требуется больше времени при компиляции.

15 голосов
/ 31 октября 2012

Краткий ответ

Для нормального кода, особенно на языке высокого уровня, таком как JavaScript , в i++ и i--.

нет разницы в производительности.

Критериями производительности являются использование в цикле for и оператор сравнения .

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

Подробное объяснение

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

Это сравнение выполняется при каждом проходе цикла и может привести к ощутимому улучшению производительности.

for(var i = array.length; i--; )

будет оцениваться как псевдокод , например:

 i=array.length
 :LOOP_START
 decrement i
 if [ i = 0 ] goto :LOOP_END
 ... BODY_CODE
 :LOOP_END

Обратите внимание, что 0 является литералом или, другими словами, постоянным значением.

for(var i = 0 ; i < array.length; i++ )

будет оцениваться в псевдокод , как это (предполагается нормальная оптимизация интерпретатора):

 end=array.length
 i=0
 :LOOP_START
 if [ i < end ] goto :LOOP_END
 increment i
 ... BODY_CODE
 :LOOP_END

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

Только мои 5 центов

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

Обычно классическая итерация из массива от начала до конца лучше.

Более быстрая итерация от массива до конца приводит к возможной нежелательной обратной последовательности.

Post scriptum

Как указано в комментарии: разница --i и i-- заключается в оценке i до или после уменьшения.

Лучшее объяснение - попробовать ;-) Вот пример Bash .

 % i=10; echo "$((--i)) --> $i"
 9 --> 9
 % i=10; echo "$((i--)) --> $i"
 10 --> 9
14 голосов
/ 30 октября 2012

Я видел ту же рекомендацию в Sublime Text 2.

Как уже было сказано, главное улучшение - это не оценка длины массива на каждой итерации в цикле for. Это хорошо известный метод оптимизации, особенно эффективный в JavaScript, когда массив является частью HTML-документа (выполняется for для всех элементов li).

Например,

for (var i = 0; i < document.getElementsByTagName('li').length; i++)

намного медленнее, чем

for (var i = 0, len = document.getElementsByTagName('li').length; i < len; i++)

С того места, где я стою, главное улучшение формы в вашем вопросе заключается в том, что она не объявляет дополнительную переменную (len в моем примере)

Но если вы спросите меня, все дело не в оптимизации i++ против i--, а в том, что вам не нужно оценивать длину массива на каждой итерации (тест производительности можно посмотреть на JSPerf ).

13 голосов
/ 30 октября 2012

Не думаю, что имеет смысл говорить, что i-- быстрее, чем i++ в JavaScript.

Прежде всего , это полностью зависит от реализации движка JavaScript.

Во-вторых, , при условии, что самые простые конструкции с JIT'ом и переведены в нативные инструкции, тогда i++ против i-- будет полностью зависеть от процессора, который его выполняет. То есть на ARM (мобильных телефонах) быстрее снижаться до 0, поскольку уменьшение и сравнение с нулем выполняются в одной инструкции.

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

for(var i = array.length; i--; )

но предложенный способ не потому, что один быстрее другого, а просто потому, что если вы напишите

for(var i = 0; i < array.length; i++)

затем на каждой итерации array.length нужно было оценить (возможно, более умный движок JavaScript мог выяснить, что цикл не изменит длину массива). Несмотря на то, что это выглядит как простое утверждение, на самом деле это какая-то функция, которая вызывается изнутри движком JavaScript.

Другая причина, по которой i-- можно считать «более быстрой», заключается в том, что движку JavaScript нужно выделить только одну внутреннюю переменную для управления циклом (переменная для var i). Если сравнивать с массивом array.length или какой-либо другой переменной, тогда для управления циклом требовалось более одной внутренней переменной, а количество внутренних переменных является ограниченным преимуществом движка JavaScript. Чем меньше переменных используется в цикле, тем больше у JIT шансов на оптимизацию. Вот почему i-- можно считать быстрее ...

13 голосов
/ 31 октября 2012

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

Итак, поехали:

Простой ответ: i--, как правило, быстрее, потому что не нужно запускать сравнение с 0 при каждом запуске, результаты теста для различных методов приведены ниже:

Результаты теста: Как доказано , этот jsPerf, arr.pop() на самом деле самый быстрый цикл на сегодняшний день. Но, сосредоточив внимание на --i, i--, i++ и ++i, как вы задали в своем вопросе, вот результаты jsPerf (они из нескольких jsPerf, см. Источники ниже):

--i и i-- одинаковы в Firefox, в то время как i-- быстрее в Chrome.

В Chrome базовый цикл for (for (var i = 0; i < arr.length; i++)) быстрее, чем i-- и --i, а в Firefox он медленнее.

Как в Chrome, так и в Firefox кэширование arr.length значительно быстрее, а Chrome опережает его примерно на 170 000 операций в секунду.

Без существенной разницы, ++i быстрее, чем i++, в большинстве браузеров, AFAIK, в любом браузере никогда не бывает наоборот.

Краткая сводка: arr.pop() - самая быстрая петля на сегодняшний день; для специально упомянутых циклов i-- - самый быстрый цикл.

Источники: http://jsperf.com/fastest-array-loops-in-javascript/15, http://jsperf.com/ipp-vs-ppi-2

Надеюсь, это ответит на ваш вопрос.

...