Как упоминалось в других ответах, CLR поддерживает оптимизацию хвостового вызова, и, похоже, исторически происходили прогрессивные улучшения. Но поддержка его в C # имеет нерешенную Proposal
проблему в репозитории git для разработки языка программирования C # Support tail recursion # 2544 .
Здесь вы можете найти полезные подробности и информацию. Например, @jaykrell упомянул
Позвольте мне поделиться тем, что я знаю.
Иногда обратный вызов - беспроигрышный результат. Это может сэкономить CPU. jmp дешевле, чем call / ret. Он может сохранить стек. Прикосновение к меньшему количеству стопки обеспечивает лучшую локальность.
Иногда хвостовой вызов - это потеря производительности, выигрыш стека. CLR имеет сложный механизм, в котором вызываемой стороне передается больше параметров, чем получено вызывающей стороной. Я имею в виду, в частности, больше места в стеке для параметров. Это медленно. Но это сохраняет стек. Он будет делать это только с хвостом. префикс.
Если параметры вызывающего объекта больше, чем параметры вызываемого объекта, это обычно довольно простое беспроигрышное преобразование. Могут быть такие факторы, как изменение позиции параметра с управляемого на целое число / число с плавающей запятой, создание точных карт StackMaps и т. Д.
Теперь есть еще один аспект - алгоритмы, требующие устранения хвостовых вызовов, чтобы иметь возможность обрабатывать произвольно большие данные с фиксированным / малым стеком. Дело не в производительности, а в способности бегать вообще.
Также позвольте мне упомянуть (в качестве дополнительной информации): когда мы генерируем скомпилированную лямбду с использованием классов выражений в System.Linq.Expressions
пространстве имен, есть аргумент с именем tailCall, который, как объясняется в его комментарии,
Логическое значение, указывающее, будет ли применяться оптимизация хвостового вызова при компиляции созданного выражения.
Я еще не пробовал это сделать, и я не уверен, как это может помочь в связи с вашим вопросом, но, вероятно, кто-то может попробовать это и может быть полезно в некоторых сценариях:
var myFuncExpression = System.Linq.Expressions.Expression.Lambda<Func< … >>(body: … , tailCall: true, parameters: … );
var myFunc = myFuncExpression.Compile();
preemptive
(например, факториальный алгоритм) иNon-preemptive
(например, функция Аккермана). Автор привел всего два примера, о которых я упомянул, без должного обоснования этой раздвоения. Эта бифуркация такая же, как хвостовые и нехвостовые рекурсивные функции?