it-swarm.dev

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

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

В конкретном случае, почему этот метод не оптимизирован в цикл ( Visual Studio 2008 32-разрядная, если это имеет значение) ?:

private static void Foo(int i)
{
    if (i == 1000000)
        return;

    if (i % 100 == 0)
        Console.WriteLine(i);

    Foo(i+1);
}
100
ripper234

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

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

Само CLR поддерживает оптимизацию хвостового вызова, но компилятор для конкретного языка должен знать, как генерировать соответствующий код операции , и JIT должен быть готов соблюдать его. F # s fsc сгенерирует соответствующие коды операций (хотя для простой рекурсии он может просто преобразовать все это в цикл while напрямую). C # не работает.

Смотрите это сообщение в блоге для некоторых деталей (вполне возможно, что сейчас устарел, учитывая недавние изменения JIT). Обратите внимание, что CLR изменяется для 4.0 x86, x64 и ia64 будут уважать это .

78
ShuggyCoUk

Это отправка отзыва в Microsoft Connect должно ответить на ваш вопрос. Он содержит официальный ответ от Microsoft, поэтому я бы рекомендовал пойти по этому.

Спасибо за предложение. Мы рассмотрели возможность использования команд хвостового вызова в ряде моментов при разработке компилятора C #. Однако есть некоторые тонкие проблемы, которые заставили нас пока избегать этого: 1) На самом деле нет нетривиальных накладных расходов на использование инструкции .tail в CLR (это не просто инструкция перехода, поскольку конечные вызовы в конечном итоге становятся во многих менее строгих средах, таких как среды выполнения функциональных языков, где хвостовые вызовы сильно оптимизированы). 2) Существует немного реальных методов C #, в которых было бы законно выдавать хвостовые вызовы (другие языки поощряют шаблоны кодирования, которые имеют больше хвостовой рекурсии, а многие, которые в значительной степени полагаются на оптимизацию хвостовых вызовов, фактически выполняют глобальную переписывание (например, преобразования Continuation Passing). ) увеличить количество хвостовой рекурсии). 3) Отчасти из-за 2) случаи, когда методы C # переполняют стек из-за глубокой рекурсии, которая должна была быть успешной, довольно редки.

Все это говорит о том, что мы продолжим рассмотрение этого вопроса и, возможно, в будущем выпуске компилятора найдут некоторые шаблоны, в которых имеет смысл выдавать инструкции .tail.

Кстати, как уже отмечалось, стоит отметить, что хвостовая рекурсия оптимизирована на x64.

71
Noldorin

C # не оптимизирован для рекурсии хвостового вызова, потому что это то, для чего F #!

Подробнее об условиях, мешающих компилятору C # выполнять оптимизацию хвостового вызова, см. В этой статье: условия хвостового вызова JIT CLR .

Совместимость между C # и F #

C # и F # взаимодействуют очень хорошо, и, поскольку среда выполнения .NET (CLR) разработана с учетом этой возможности взаимодействия, каждый язык разработан с оптимизацией, специфичной для его цели и задач. Для примера, который показывает, как легко вызвать код F # из кода C #, смотрите Вызов кода F # из кода C # ; пример вызова функций C # из кода F # см. в разделе вызов функций C # из F # .

Взаимодействие делегатов см. В этой статье: Взаимодействие делегатов между F #, C # и Visual Basic .

Теоретические и практические различия между C # и F #

Вот статья, которая покрывает некоторые различия и объясняет конструктивные различия рекурсии хвостового вызова между C # и F #: Генерация кода операции Tail-Call в C # и F # .

Вот статья с некоторыми примерами в C #, F # и C++\CLI: Приключения в Tail Recursion в C #, F # и C++\CLI

Основное теоретическое отличие состоит в том, что C # разработан с использованием циклов, тогда как F # разработан на принципах лямбда-исчисления. Очень хорошую книгу о принципах лямбда-исчисления можно найти в этой бесплатной книге: Структура и интерпретация компьютерных программ Абельсона, Суссмана и Суссмана .

Для очень хорошей вводной статьи по хвостовым вызовам в F # см. Эту статью: Подробное введение в хвостовые вызовы в F # . Наконец, вот статья, в которой рассматривается различие между рекурсией без хвоста и рекурсией с хвостовым вызовом (в F #): рекурсия с хвостом против рекурсии без хвоста в F Sharp .

14
devinbost

Мне недавно сказали, что компилятор C # для 64-битных систем оптимизирует хвостовую рекурсию.

C # также реализует это. Причина, по которой это не всегда применяется, состоит в том, что правила, применяемые для применения хвостовой рекурсии, очень строги.

8
Alexandre Brisebois

Вы можете использовать техника батута для хвостовых рекурсивных функций в C # (или Java). Однако лучшее решение (если вы просто заботитесь об использовании стека) - использовать метод этот маленький помощник , чтобы обернуть части одной и той же рекурсивной функции и сделать ее итеративной, сохраняя читаемость функции.

3
naiem