Я изучаю F #, и это начинает влиять на то, как я думаю, когда я программирую на C #. С этой целью я использовал рекурсию, когда чувствую, что результат улучшает читабельность, и я не могу представить, что это приведет к переполнению стека.
Это заставляет меня спросить, могут ли компиляторы автоматически преобразовывать рекурсивные функции в эквивалентную нерекурсивную форму?
programming-languages
compiler
recursion
Аарон Анодид
источник
источник
return recursecall(args);
для рекурсии, тем более сложный материал возможно путем создания явного стека и наматывая его вниз, но я сомневаюсь , что они будутОтветы:
Да, некоторые языки и компиляторы преобразуют рекурсивную логику в нерекурсивную логику. Это известно как оптимизация хвостовых вызовов - обратите внимание, что не все рекурсивные вызовы являются оптимизируемыми хвостовыми вызовами. В этой ситуации компилятор распознает функцию вида:
Здесь язык способен распознавать, что возвращаемый результат является результатом другой функции, и изменять вызов функции с новым кадром стека в переход.
Поймите, что классический метод факториала:
не является хвостовым вызовом, оптимизируемым из-за проверки, необходимой при возврате.
Чтобы сделать этот хвостовой вызов оптимизируемым,
Компиляция этого кода с помощью
gcc -O2 -S fact.c
(-O2 необходима для включения оптимизации в компиляторе, но с большей оптимизацией -O3 человеку становится трудно читать ...)Можно увидеть в сегменте
.L4
,jne
а неcall
(который выполняет вызов подпрограммы с новым кадром стека).Обратите внимание, что это было сделано с C. Оптимизация вызовов Tail в java сложна и зависит от реализации JVM - tail-recursion + java и tail-recursion + оптимизация - хорошие наборы тегов для просмотра. Вы можете найти другие языки JVM способны оптимизировать хвостовую рекурсию лучше (попытка Clojure (который требует повторялся для оптимизации хвостового вызова), или Скале).
источник
Действуй осторожно.
Ответ да, но не всегда, и не все из них. Это техника, которая называется несколько разных имен, но вы можете найти довольно определенную информацию здесь и в Википедии .
Я предпочитаю название «Оптимизация вызовов», но есть и другие, и некоторые люди будут путать этот термин.
Тем не менее, есть пара важных вещей для реализации:
Чтобы оптимизировать оконечный вызов, оконечный вызов требует параметров, которые известны во время вызова. Это означает, что если один из параметров является вызовом самой функции, то он не может быть преобразован в цикл, поскольку это потребует произвольного вложения указанного цикла, который не может быть раскрыт во время компиляции.
C # не надежно оптимизирует хвостовые вызовы. У IL есть инструкция, чтобы сделать это, что будет генерировать компилятор F #, но компилятор C # будет генерировать его непоследовательно, и в зависимости от ситуации JIT, JIT может или не может делать это вообще. Все указывает на то, что вы не должны полагаться на свои хвостовые вызовы, оптимизированные в C #, риск переполнения при этом является значительным и реальным
источник