Является ли итерация быстрее, чем рекурсия, или просто менее подвержена переполнению стека? - PullRequest
11 голосов
/ 28 февраля 2012

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

Но если переполнение стека не является проблемой (потому что вы не выполняете рекурсивный анализ), есть ли причина предпочесть итеративное, а не рекурсивное? Это быстрее?

Меня больше всего интересует JavaScript на V8.

Ответы [ 3 ]

20 голосов
/ 28 февраля 2012

В Javascript, который не выполняет (не требуется и, возможно, не может видеть комментарии) оптимизацию хвостовой рекурсии, рекурсия медленнее, чем итерация (потому что почти во всех языках вызов функциина намного дороже, чем прыжок) и имеет возможность вызывать ошибки переполнения стека, если вы выполняете слишком глубокую рекурсию (однако, предел может быть достаточно глубоким; в моем эксперименте Chrome отказался на глубине рекурсии 16 316).

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

4 голосов
/ 28 февраля 2012

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

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

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

В тестах на http://jsperf.com/function-call-overhead-test пытаются количественно оценить издержки вызова функций в различных интерпретаторах JS.Я бы собрал аналогичный тест, который явно тестирует рекурсию, если вы беспокоитесь.


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

Например,простая реализация свертывания массива в JavaScript:

function fold(f, x, i, arr) {
  if (i === arr.length) { return x; }
  var nextX = f(x, arr[i]);
  return fold(f, nextX, i+1, arr);
}

нельзя оптимизировать хвостом, потому что вызов

 fold(eval, 'var fold=alert', 0, [0])

будет eval('var fold=alert') внутри тела fold, вызываяказалось бы, хвостово-рекурсивный вызов fold не является рекурсивным.

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

1 голос
/ 10 июня 2013

Это зависит ... Я задавал тот же вопрос, когда у меня возникала ошибка "Slow script" в IE8 в рекурсивной функции.И был удивлен, что итерация на самом деле была еще медленнее.

Я шел по дереву в поисках определенного узла.И я переписал свою рекурсивную функцию итеративным способом (используя стек для сохранения контекста), используя аналогичный подход: https://stackoverflow.com/a/159777/1245231

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

Причиной этого может быть то, что использование стека вызовов метода в JS, вероятно, быстрее, чем использование массива с соответствующими операциями push () и pop () в цикле.Чтобы убедиться в этом, я создал тест, который имитирует обход дерева в обоих случаях: http://jsperf.com/recursion-vs-iteration-852 Результаты удивительны.В то время как в Chrome итеративная версия (в моем случае) на 19% медленнее, в IE8 итеративная версия на 65% медленнее, чем рекурсивная версия.

...