Почему .NET/С# не оптимизируется для рекурсии хвоста?

Я нашел этот вопрос, о том, какие языки оптимизируют хвостовую рекурсию. Почему С# не оптимизирует хвостовую рекурсию, когда это возможно?

Для конкретного случая, почему этот метод не оптимизирован в цикл (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). Однако лучшее решение (если вы просто заботитесь об использовании стека) должно использовать этот небольшой помощник, чтобы обернуть части одной и той же рекурсивной функции и сделать он итеративный, сохраняя читаемую функцию.