Рекурсивные функции, переполнения стека и Y-комбинаторы - PullRequest
5 голосов
/ 02 декабря 2011

У меня есть рекурсивная функция (в C #), которую мне нужно вызывать около 800 миллионов раз; это, очевидно, обычно приводит к переполнению стека после примерно 900-го вызова. Я выкинул это из нескольких циклов, но рекурсивный шаблон намного проще и проще в обслуживании.

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

У кого-нибудь есть опыт использования y-combinator? Буду ли я застревать в переполнении стека?

Возьмите простой пример факториала. Факториал для большинства чисел больше 5000 вызовет переполнение стека. Если бы я правильно использовал y-комбинатор в этом сценарии, это исправило бы переполнение стека?

Это не кажется тривиальной реализацией, поэтому я хочу подтвердить это, прежде чем тратить усилия на разработку / ресурсы на реализацию и изучение y-combinator.

Ответы [ 4 ]

6 голосов
/ 02 декабря 2011

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

// Tail
int factorial( int n ) { return factorialTail( n, 1, 1 ); }
int factorialTail( int n, int i, int acc ) {
  if ( n < i ) return acc;
  else return factorialTail( n, i + 1, acc * i );
}

// Trampoline
int factorialTramp( int n ) { 
   var bounce = new Tuple<bool,int,int>(true,1,1);
   while( bounce.Item1 ) bounce = factorialOline( n, bounce.Item2, bounce.Item3 );
   return bounce.Item3;
}
Tuple<bool,int,int> factorialOline( int n, int i, int acc ) {
  if ( n < i ) return new Tuple<bool,int,int>(false,i,acc);
  else return new Tuple<bool,int,int>(true,i + 1,acc * i);
}
6 голосов
/ 02 декабря 2011

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

Цикл - это то, что вы ищете, если только вы не используете среду выполнения, которая предлагает правильную хвостовую рекурсию (такую ​​как схема).

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

5 голосов
/ 02 декабря 2011

Вы можете использовать батут, как в Reactive Extension, я пытался объяснить это в своем блоге http://rohiton.net/2011/08/15/trampoline-and-reactive-extensions/

1 голос
/ 02 декабря 2011

Одним из решений является преобразование вашей функции (й) в использование цикла и явной структуры данных context . Контекст представляет то, что люди обычно считают стеком вызовов. Возможно, вы найдете мой ответ на другой вопрос о преобразовании в хвостовую рекурсию полезным. Соответствующие шаги с 1 по 5; 6 и 7 специфичны для этой конкретной функции, в то время как ваша звучит более сложно.

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

...