Я нашел этот вопрос, о том, какие языки оптимизируют хвостовую рекурсию. Почему С# не оптимизирует хвостовую рекурсию, когда это возможно?
Для конкретного случая, почему этот метод не оптимизирован в цикл (Visual Studio 2008 32-бит, если это имеет значение ):
private static void Foo(int i)
{
if (i == 1000000)
return;
if (i % 100 == 0)
Console.WriteLine(i);
Foo(i+1);
}
Ответ 1
Компиляция JIT - сложный процесс балансировки между тем, чтобы не тратить слишком много времени на этап компиляции (тем самым значительно замедляя краткосрочные приложения), а не делать достаточно анализа, чтобы поддерживать конкурентоспособность приложения в долгосрочной перспективе со стандартным опережением -time компиляции.
Интересно, что шаги компиляции NGen не нацелены на более агрессивную оптимизацию. Я подозреваю, что это потому, что они просто не хотят иметь ошибки, в которых поведение зависит от того, отвечает ли JIT или NGen за машинный код.
сама CLR поддерживает оптимизацию хвостовых вызовов, но компилятор, специфичный для языка, должен знать, как создать соответствующий opcode, и JIT должен быть готов его уважать.
F # fsc генерирует соответствующие коды операций (хотя для простой рекурсии он может просто преобразовать всю вещь в цикл while
). С# csc не делает.
См. этот пост в блоге для некоторых деталей (возможно, теперь устарел, учитывая недавние изменения JIT). Обратите внимание, что CLR изменяется на 4.0 x86, x64 и ia64 будут уважать его.
Ответ 2
Этот Отправка комментариев Microsoft Connect должен ответить на ваш вопрос. Он содержит официальный ответ от Microsoft, поэтому я бы рекомендовал это сделать.
Спасибо за предложение. У нас рассмотренный исходящий хвостовой вызов инструкции в ряде пунктов в разработка компилятора С#. Однако есть некоторые тонкие проблемы которые побудили нас избежать этого далеко: 1) На самом деле существует нетривиальные накладные расходы на использование .tail в CLR (это не просто инструкция прыжка как хвост звонки в конечном итоге становятся во многих строгие условия, такие как функциональные языковые среды выполнения, где хвостовые звонки сильно оптимизированы). 2) Существует несколько реальных методов С#, где было бы законно испускать хвостовые звонки (другие языки поощряют кодирование шаблоны, которые имеют больше хвоста рекурсии, и многие из них сильно зависят на оптимизации хвостового вызова на самом деле делают глобальное переписывание (например, Продолжение Прохождение преобразований) увеличить количество хвоста рекурсия). 3) Отчасти из-за 2), случаи, когда методы стека С# переполняют из-за глубокой рекурсии, которая должна иметь преуспели довольно редко.
Все, что говорилось, мы продолжаем смотреть на это, и мы можем в будущем выпуске компилятора найти некоторые шаблоны где имеет смысл испускать .tail инструкции.
Кстати, как уже отмечалось, стоит отметить, что хвостовая рекурсия оптимизирована на x64.
Ответ 3
С# не оптимизируется для рекурсии хвостового вызова, потому что для этого используется F #!
Для некоторой глубины условий, которые не позволяют компилятору С# выполнять оптимизацию хвостового вызова, см. эту статью: Условия хвостового вызова JIT CLR.
Взаимодействие между С# и F #
С# и F # взаимодействуют очень хорошо, и поскольку среда .NET Common Language Runtime (CLR) разработана с учетом этой функциональной совместимости, каждый язык разработан с оптимизацией, характерной для ее целей и целей. Пример, который показывает, как легко вызвать код F # из кода С#, см. Вызов кода F # из кода С#; для примера вызова функций С# из кода F #, см. Вызов функций С# из F #.
Для взаимодействия с делегатами см. эту статью: Делегировать взаимодействие между F #, С# и Visual Basic.
Теоретические и практические различия между С# и F #
Вот статья, которая охватывает некоторые различия и объясняет различия в дизайне рекурсии хвостового вызова между С# и F #: Генерирование кода хвоста в С# и F #.
Вот статья с некоторыми примерами в С#, F # и С++\CLI: Приключения в рекурсии хвоста в С#, F # и С++\CLI
Основное теоретическое различие заключается в том, что С# спроектирован с петлями, тогда как F # разработан по принципам исчисления лямбда. Для очень хорошей книги о принципах лямбда-исчисления см. Эту бесплатную книгу: Структура и интерпретация компьютерных программ, Абельсон, Суссман и Суссман.
Для очень хорошей вводной статьи о хвостовых вызовах в F # см. эту статью: Подробное введение в Tail Calls в F #. Наконец, вот статья, которая описывает разницу между нерегулярной рекурсией и рекурсией хвостового вызова (в F #): Репликация хвоста и нерегулярной рекурсии в F sharp.
Ответ 4
Недавно мне сказали, что компилятор С# для 64 бит оптимизирует хвостовую рекурсию.
С# также реализует это. Причина, по которой это не всегда применяется, заключается в том, что правила, используемые для применения хвостовой рекурсии, очень строги.
Ответ 5
Вы можете использовать технику trampoline для хвостовых рекурсивных функций на С# (или Java). Однако лучшее решение (если вы просто заботитесь об использовании стека) должно использовать этот небольшой помощник, чтобы обернуть части одной и той же рекурсивной функции и сделать он итеративный, сохраняя читаемую функцию.