Я нашел этот вопрос о том, какие языки оптимизируют хвостовую рекурсию. Почему C # по возможности не оптимизирует хвостовую рекурсию?
В конкретном случае, почему этот метод не оптимизирован для создания цикла ( 32-разрядная версия Visual Studio 2008 , если это имеет значение) ?:
private static void Foo(int i)
{
if (i == 1000000)
return;
if (i % 100 == 0)
Console.WriteLine(i);
Foo(i+1);
}
c#
.net
optimization
tail-recursion
ripper234
источник
источник
preemptive
(например, факториальный алгоритм) иNon-preemptive
(например, функция Аккермана). Автор привел всего два примера, о которых я упомянул, без должного обоснования этой раздвоения. Эта бифуркация такая же, как хвостовые и нехвостовые рекурсивные функции?Ответы:
JIT-компиляция - это сложный баланс между тем, чтобы не тратить слишком много времени на этап компиляции (таким образом, значительно замедляя краткосрочные приложения), и не проводить достаточный анализ, чтобы поддерживать приложение конкурентоспособным в долгосрочной перспективе со стандартной опережающей компиляцией .
Интересно, что шаги компиляции NGen не нацелены на более агрессивную оптимизацию. Я подозреваю, что это происходит потому, что они просто не хотят иметь ошибок, поведение которых зависит от того, отвечает ли JIT или NGen за машинный код.
Сама среда CLR поддерживает оптимизацию хвостового вызова, но компилятор, зависящий от языка, должен знать, как сгенерировать соответствующий код операции, и JIT должен быть готов его уважать. F # F # будет генерировать соответствующие коды операций (хотя для простой рекурсии он может просто преобразовать все это в
while
цикл напрямую). C # в CSC нет.См. Это сообщение в блоге для получения некоторых подробностей (вполне возможно, что оно устарело, учитывая недавние изменения JIT). Обратите внимание, что CLR изменяется для 4.0, x86, x64 и ia64 будут уважать это .
источник
Этот отправленный отзыв Microsoft Connect должен ответить на ваш вопрос. Он содержит официальный ответ от Microsoft, поэтому я бы рекомендовал пойти по нему.
Кстати, как это было указано, стоит отметить , что хвостовая рекурсия будет оптимизирована на x64.
источник
C # не оптимизирован для рекурсии хвостового вызова, потому что для этого и предназначен F #!
Более подробно об условиях, которые не позволяют компилятору C # выполнять оптимизацию хвостового вызова, см. В этой статье: Условия хвостового вызова JIT CLR .
Взаимодействие между C # и F #
C # и F # очень хорошо взаимодействуют, и поскольку среда .NET Common Language Runtime (CLR) разработана с учетом этой возможности взаимодействия, каждый язык разработан с оптимизацией, специфичной для его целей и задач. Пример, показывающий, насколько легко вызвать код F # из кода C #, см. В разделе Вызов кода F # из кода C # ; пример вызова функций C # из кода F # см. в разделе « Вызов функций C # из F #» .
Информацию о взаимодействии делегатов см. В этой статье: Взаимодействие делегатов между F #, C # и Visual Basic .
Теоретические и практические различия между C # и F #
Вот статья, в которой рассматриваются некоторые различия и объясняются различия в конструкции рекурсии хвостового вызова между C # и F #: Генерация кода операции хвостового вызова в C # и F # .
Вот статья с некоторыми примерами на C #, F # и C ++ \ CLI: Adventures in Tail Recursion в C #, F # и C ++ \ CLI
Основное теоретическое различие заключается в том, что C # разработан с использованием циклов, тогда как F # разработан на принципах лямбда-исчисления. Если вам нужна очень хорошая книга по принципам лямбда-исчисления, см. Эту бесплатную книгу: « Структура и интерпретация компьютерных программ» Абельсона, Сассмана и Сассмана .
Очень хорошую вводную статью о хвостовых вызовах в F # см. В этой статье: Подробное введение в хвостовые вызовы в F # . Наконец, вот статья, которая описывает разницу между рекурсией без хвоста и рекурсией с хвостовым вызовом (в F #): хвостовая рекурсия и не хвостовая рекурсия в F-диез .
источник
Недавно мне сказали, что компилятор C # для 64-битной версии оптимизирует хвостовую рекурсию.
C # также реализует это. Причина, по которой он не всегда применяется, заключается в том, что правила, используемые для применения хвостовой рекурсии, очень строгие.
источник
Вы можете использовать технику трамплина для хвостовых рекурсивных функций в C # (или Java). Однако лучшее решение (если вы просто заботитесь об использовании стека) - использовать этот небольшой вспомогательный метод, чтобы обернуть части одной и той же рекурсивной функции и сделать ее итеративной, сохраняя при этом функцию читаемой.
источник
Как упоминалось в других ответах, CLR поддерживает оптимизацию хвостового вызова, и, похоже, исторически происходили прогрессивные улучшения. Но поддержка его в C # имеет нерешенную
Proposal
проблему в репозитории git для разработки языка программирования C # Support tail recursion # 2544 .Здесь вы можете найти полезные подробности и информацию. Например, @jaykrell упомянул
Также позвольте мне упомянуть (в качестве дополнительной информации): когда мы генерируем скомпилированную лямбду с использованием классов выражений в
System.Linq.Expressions
пространстве имен, есть аргумент с именем tailCall, который, как объясняется в его комментарии,Я еще не пробовал это сделать, и я не уверен, как это может помочь в связи с вашим вопросом, но, вероятно, кто-то может попробовать это и может быть полезно в некоторых сценариях:
источник