Comme d'autres réponses l'ont mentionné, CLR prend en charge l'optimisation des appels de queue et il semble qu'il faisait historiquement l'objet d'améliorations progressives. Mais sa prise en charge en C # a un Proposal
problème ouvert dans le référentiel git pour la conception du langage de programmation C # Support tail recursion # 2544 .
Vous pouvez y trouver quelques détails et informations utiles. Par exemple @jaykrell mentionné
Laissez-moi vous dire ce que je sais.
Parfois, le tailcall est une performance gagnant-gagnant. Cela peut économiser du processeur. jmp est moins cher que call / ret Il peut économiser la pile. Toucher moins de pile améliore la localisation.
Parfois, le tailcall est une perte de performance, gain de pile. Le CLR a un mécanisme complexe dans lequel passer plus de paramètres à l'appelé que l'appelant a reçu. Je veux dire spécifiquement plus d'espace de pile pour les paramètres. C'est lent. Mais il conserve la pile. Il ne le fera qu'avec la queue. préfixe.
Si les paramètres de l'appelant sont plus grands que ceux de l'appelé, il s'agit généralement d'une transformation gagnant-gagnant assez facile. Il peut y avoir des facteurs tels que le changement de position de paramètre de géré à entier / flottant, et la génération de StackMaps précis et autres.
Maintenant, il y a un autre angle, celui des algorithmes qui exigent l'élimination des appels de queue, dans le but de pouvoir traiter des données arbitrairement grandes avec une pile fixe / petite. Il ne s'agit pas du tout de performances, mais de capacité à fonctionner.
Permettez-moi également de mentionner (comme info supplémentaire), lorsque nous System.Linq.Expressions
générons un lambda compilé en utilisant des classes d'expression dans l' espace de noms, il y a un argument nommé 'tailCall' qui, comme expliqué dans son commentaire, est
Un booléen qui indique si l'optimisation de l'appel de fin sera appliquée lors de la compilation de l'expression créée.
Je ne l'ai pas encore essayé, et je ne sais pas comment cela peut aider à répondre à votre question, mais quelqu'un peut probablement l'essayer et peut être utile dans certains scénarios:
var myFuncExpression = System.Linq.Expressions.Expression.Lambda<Func< … >>(body: … , tailCall: true, parameters: … );
var myFunc = myFuncExpression.Compile();
preemptive
(par exemple l'algorithme factoriel) etNon-preemptive
(par exemple la fonction d'ackermann). L'auteur n'a donné que deux exemples que j'ai mentionnés sans donner un raisonnement approprié derrière cette bifurcation. Cette bifurcation est-elle la même que les fonctions récursives de queue et non-queue?