Циклы действительно быстрее наоборот? - 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 ]

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

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

В некоторых случаях доступ к элементам массива в порядке столбцов происходит быстрее, чем в строке, из-за увеличения коэффициента попадания.

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

В последний раз я беспокоился об этом, когда писал 6502 сборка (8-битная, да!). Большим преимуществом является то, что большинство арифметических операций (особенно декрементов) обновляли набор флагов, одним из которых был Z, индикатор «достигнут нуля».

Итак, в конце цикла вы просто выполнили две инструкции: DEC (уменьшение) и JNZ (переход, если не ноль), сравнение не требуется!

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

Короче говоря: Нет абсолютно никакой разницы в том, чтобы делать это в JavaScript.

Прежде всего, вы можете проверить это сами:

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

Итак, насколько вы можете видеть, нет никакой разницы между производительностью в любой среде.

Если вы хотите улучшить производительность вашего скрипта, попробуйте сделать следующее:

  1. Используйте оператор var a = array.length;, чтобы вы не вычисляли его значение каждый раз в цикле
  2. Развернуть цикл http://en.wikipedia.org/wiki/Loop_unwinding

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

Мое собственное мнение, почему появилось такое заблуждение (Dec против Inc)

Давным-давно была общая машинная инструкция, DSZ (Decrement и Skip on Zero). Люди, которые программировали на ассемблере, использовали эту инструкцию для реализации циклов, чтобы сохранить регистр. Теперь эти древние факты устарели, и я уверен, что вы не получите никакого улучшения производительности ни на одном языке, используя это псевдо улучшение.

Я думаю, что единственный способ распространения таких знаний в наше время - это когда вы читаете чужой код. Посмотрите на такую ​​конструкцию и спросите, почему она была реализована, и вот ответ: «это улучшает производительность, потому что она сравнивается с нулем». Вы растерялись из-за высшего знания своего коллеги и думаете использовать его, чтобы быть намного умнее :-)

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

Это не зависит от знака -- или ++, но зависит от условий, применяемых в цикле.

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

Но не беспокойтесь об этой оптимизации, потому что на этот раз ее эффект измеряется в наносекундах.

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

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

Кроме того, если вы можете гарантировать, что count всегда >= 0, вы можете упростить до:

for (var i = count; i--;)
{
  // whatever
}
6 голосов
/ 30 октября 2012

for(var i = array.length; i--; ) не намного быстрее. Но когда вы заменяете array.length на super_puper_function(), это может быть значительно быстрее (так как он вызывается на каждой итерации). В этом разница.

Если вы собираетесь изменить его в 2014 году, вам не нужно думать об оптимизации. Если вы собираетесь изменить его с помощью функции «Поиск и замена», вам не нужно думать об оптимизации. Если у вас нет времени, вам не нужно думать об оптимизации. Но теперь у вас есть время подумать об этом.

P.S .: i-- не быстрее, чем i++.

5 голосов
/ 22 января 2015

Я сделал сравнение на jsbench .

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

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

вы оцениваете .length каждый раз, когда увеличиваете i. В этом:

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

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

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

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

Цикл с вызовом функции (определяется в другом месте):

for (i = values.length; i-- ;) {
  add( values[i] );
}

Петля со встроенным кодом:

var sum = 0;
for ( i = values.length; i-- ;) {
  sum += values[i];
}

Если вы можете встроить свой код вместо вызова функции, не жертвуя удобочитаемостью, вы можете сделать цикл на порядок быстрее!


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

  1. Узкое место может быть в другом месте (ajax, reflow, ...)
  2. Вы можете выбрать лучший алгоритм
  3. Вы можете выбрать лучшую структуру данных

Но помните:

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

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

То, как вы делаете это сейчас, не быстрее (кроме того, что это неопределенный цикл, я думаю, вы хотели сделать i--.

Если вы хотите сделать это быстрее, сделайте:

for (i = 10; i--;) {
    //super fast loop
}

конечно, вы не заметите это на такой маленькой петле. Причина в том, что он быстрее, потому что вы уменьшаете значение i, проверяя, что оно «true» (оно оценивается как «false», когда достигает 0)

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

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

Обычный способ кодирования цикла for для обработки массива lis выглядит так:

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

Проблема в том, что для оценки длины массива с использованием myArray.length требуется время, а способ, которым мы закодировали цикл, означает, что эта оценка должна выполняться каждый раз вокруг цикла. Если массив содержит 1000 элементов, длина массива будет оценена 1001 раз. Если бы мы смотрели на переключатели и имели myForm.myButtons.length, то это займет еще больше времени для оценки, поскольку соответствующая группа кнопок в указанной форме должна сначала быть найдена, прежде чем можно будет оценивать длину каждый раз вокруг цикла.

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

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

Код для этого:

for (var i = 0, var j = myArray.length; i < j; i++) {...

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

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

Последний код, который обрабатывает наш массив наиболее эффективным способом:

for (var i = myArray.length-1; i > -1; i--) {...

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

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

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

В низкоуровневой сборке есть инструкция зацикливания, называемая DJNZ (уменьшение и переход, если не ноль). Таким образом, декремент и переход - все в одной инструкции, что делает его, возможно, немного быстрее, чем INC и JL / JB (инкремент, переход, если меньше / прыжок, если ниже). Кроме того, сравнение с нулем проще, чем сравнение с другим числом. Но все это действительно незначительно, а также зависит от целевой архитектуры (может иметь значение, например, для руки в смартфоне).

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

...