J'ai trouvé cette question sur les langues optimisant la récursivité de la queue. Pourquoi C # n'optimise pas la récursivité de queue, chaque fois que possible?
Pour un cas concret, pourquoi cette méthode n'est-elle pas optimisée en boucle ( Visual Studio 2008 32 bits, si cela compte)?:
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
la source
la source
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?Réponses:
La compilation JIT est un équilibre délicat entre ne pas passer trop de temps à faire la phase de compilation (ralentissant ainsi considérablement les applications de courte durée) et ne pas faire suffisamment d'analyses pour maintenir l'application compétitive à long terme avec une compilation standard à l'avance .
Fait intéressant, le NGen compilation ne visent pas à être plus agressives dans leurs optimisations. Je soupçonne que c'est parce qu'ils ne veulent tout simplement pas avoir de bogues où le comportement dépend du fait que le JIT ou NGen soit responsable du code machine.
Le CLR lui-même prend en charge l'optimisation des appels de fin, mais le compilateur spécifique au langage doit savoir comment générer l' opcode pertinent et le JIT doit être prêt à le respecter. Le fsc de F # générera les opcodes appropriés (bien que pour une simple récursion, il puisse simplement convertir le tout en
while
boucle directement). Le csc de C # ne le fait pas.Voir ce billet de blog pour quelques détails (très probablement maintenant obsolète compte tenu des récents changements JIT). Notez que le CLR change pour 4.0 les x86, x64 et ia64 le respecteront .
la source
Cette soumission de commentaires sur Microsoft Connect doit répondre à votre question. Il contient une réponse officielle de Microsoft, je vous recommande donc de suivre cela.
En passant, comme cela a été souligné, il est intéressant de noter que la récursivité de queue est optimisée sur x64.
la source
C # n'optimise pas la récursivité des appels de fin car c'est à cela que sert F #!
Pour plus d'informations sur les conditions qui empêchent le compilateur C # d'effectuer des optimisations des appels de fin, consultez cet article: Conditions d'appel de fin JIT CLR .
Interopérabilité entre C # et F #
C # et F # interopèrent très bien, et comme le CLR (Common Language Runtime) .NET est conçu avec cette interopérabilité à l'esprit, chaque langage est conçu avec des optimisations spécifiques à son intention et à ses objectifs. Pour un exemple qui montre à quel point il est facile d'appeler du code F # à partir du code C #, consultez Appeler du code F # à partir du code C # ; pour un exemple d'appel de fonctions C # à partir du code F #, consultez Appel de fonctions C # à partir de F # .
Pour l'interopérabilité des délégués, consultez cet article: Interopérabilité des délégués entre F #, C # et Visual Basic .
Différences théoriques et pratiques entre C # et F #
Voici un article qui couvre certaines des différences et explique les différences de conception de la récursivité des appels de queue entre C # et F #: Génération de l'opcode Tail-Call en C # et F # .
Voici un article avec quelques exemples en C #, F # et C ++ \ CLI: Adventures in Tail Recursion en C #, F # et C ++ \ CLI
La principale différence théorique est que C # est conçu avec des boucles tandis que F # est conçu sur les principes du calcul Lambda. Pour un très bon livre sur les principes du calcul Lambda, voir ce livre gratuit: Structure et interprétation des programmes informatiques, par Abelson, Sussman et Sussman .
Pour un très bon article d'introduction sur les appels de queue en F #, consultez cet article: Introduction détaillée aux appels de queue en F # . Enfin, voici un article qui couvre la différence entre la récursivité sans queue et la récursion d'appel de queue (en F #): la récursivité de queue vs la récursivité sans queue en F sharp .
la source
On m'a récemment dit que le compilateur C # pour 64 bits optimise la récursivité de la queue.
C # implémente également cela. La raison pour laquelle elle n'est pas toujours appliquée est que les règles utilisées pour appliquer la récursivité de queue sont très strictes.
la source
Vous pouvez utiliser la technique du trampoline pour les fonctions récursives de queue en C # (ou Java). Cependant, la meilleure solution (si vous vous souciez seulement de l'utilisation de la pile) est d'utiliser cette petite méthode d' aide pour envelopper des parties de la même fonction récursive et la rendre itérative tout en gardant la fonction lisible.
la source
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é
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, estJe 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:
la source