Como outras respostas mencionadas, CLR oferece suporte à otimização de chamada final e parece que historicamente sofreu melhorias progressivas. Mas suportá-lo em C # tem um Proposal
problema aberto no repositório git para o design da linguagem de programação C # Support tail recursion # 2544 .
Você pode encontrar alguns detalhes e informações úteis lá. Por exemplo @jaykrell mencionado
Deixe-me dar o que eu sei.
Às vezes, o tailcall é um ganho de desempenho. Isso pode economizar CPU. jmp é mais barato do que call / ret Pode economizar pilha. Tocar em menos pilha melhora a localização.
Às vezes, o tailcall é uma perda de desempenho, uma vitória de pilha. O CLR tem um mecanismo complexo no qual passa mais parâmetros ao receptor do que aquele que recebeu. Quero dizer especificamente mais espaço de pilha para parâmetros. Isso é lento. Mas conserva pilha. Ele só fará isso com a cauda. prefixo.
Se os parâmetros do chamador forem maiores que os parâmetros do chamador, geralmente é uma transformação ganha-ganha muito fácil. Pode haver fatores como a alteração da posição do parâmetro de gerenciado para inteiro / flutuante e gerar StackMaps precisos e outros.
Agora, há outro ângulo, aquele dos algoritmos que exigem a eliminação do tailcall, para fins de serem capazes de processar dados arbitrariamente grandes com pilha fixa / pequena. Não se trata de desempenho, mas sim de capacidade de execução.
Também deixe-me mencionar (como informação extra), quando estamos gerando um lambda compilado usando classes de expressão no System.Linq.Expressions
namespace, há um argumento chamado 'tailCall' que, conforme explicado em seu comentário, é
Um booleano que indica se a otimização da chamada final será aplicada ao compilar a expressão criada.
Ainda não experimentei e não tenho certeza de como pode ajudar em relação à sua dúvida, mas provavelmente alguém pode tentar e pode ser útil em alguns cenários:
var myFuncExpression = System.Linq.Expressions.Expression.Lambda<Func< … >>(body: … , tailCall: true, parameters: … );
var myFunc = myFuncExpression.Compile();
preemptive
(por exemplo, algoritmo fatorial) eNon-preemptive
(por exemplo, função de ackermann). O autor deu apenas dois exemplos que mencionei, sem dar um raciocínio adequado por trás dessa bifurcação. Essa bifurcação é igual às funções recursivas caudas e não caudais?