Y a-t-il une raison technique pour laquelle C# n'émet pas de "queue"."CIL de l'enseignement? [dupliquer]

possibilité de duplication:

pourquoi .net / c# n'élimine-t-il pas la récursion de la queue?

prendre le code C suivant:

using System;

namespace TailTest
{
    class MainClass
    {
        public static void Main (string[] args)
        {
            Counter(0);
        }

        static void Counter(int i)
        {
            Console.WriteLine(i);
            if (i < int.MaxValue) Counter(++i);
        }
    }
}

le compilateur C# (mine anyway) compilera la méthode Counter dans le CIL suivant:

.method private static hidebysig default void Counter (int32 i) cil managed 
{
.maxstack 8
IL_0000:  ldarg.0 
IL_0001:  call void class [mscorlib]System.Console::WriteLine(int32)
IL_0006:  ldarg.0 
IL_0007:  ldc.i4 2147483647
IL_000c:  bge IL_0019
IL_0011:  ldarg.0 
IL_0012:  ldc.i4.1 
IL_0013:  add 
IL_0014:  call void class TailTest.MainClass::Counter(int32)
IL_0019:  ret 
}

Le problème avec le code ci-dessus est qu'il va provoquer un débordement de pile (à environ i = 262000 sur mon matériel). Pour contourner ce problème, certains langages font ce qu'on appelle l'élimination des appels de queue ou l'optimisation des appels de queue (TCO). Essentiellement, ils transforment l'appel récursif en une boucle.

D'après ce que j'ai compris, l'implémentation 64 bits du JIT.net 4 exécute maintenant TCO et évite le débordement sur le code comme le cil ci-dessus. Cependant, le JIT 32 bits ne le fait pas. Mono ne semble pas non plus. Il est intéressant que le JIT (qui est sous temps et ressources pressure) fait TCO alors que le compilateur C# ne le fait pas. Ma question est pourquoi le compilateur C# n'est-il pas plus conscient de TCO?

il y a une instruction du CAI qui dit au JIT d'exécuter TCO. Par exemple, le compilateur C# pourrait générer le CIL suivant:

.method private static hidebysig default void Counter (int32 i) cil managed 
{
.maxstack 8
IL_0000:  ldarg.0 
IL_0001:  call void class [mscorlib]System.Console::WriteLine(int32)
IL_0006:  ldarg.0 
IL_0007:  ldc.i4 2147483647
IL_000c:  bge IL_001c
IL_0011:  ldarg.0 
IL_0012:  ldc.i4.1 
IL_0013:  add 
IL_0014:  tail.
IL_0017:  call void class TailTest.MainClass::Counter(int32)
IL_001c:  ret 
}

contrairement à l'original, ce code ne débordera pas et sera exécuté même sur le JIT 32 bits (à la fois .NET et Mono). La magie se trouve dans l'instruction CIL tail. . Les compilateurs comme F# généreront CIL qui inclut cette instruction automatiquement.

donc ma question Est, y a-t-il une raison technique pour que le compilateur C# ne fasse pas cela?

je comprends qu'historiquement, ça n'en valait peut-être pas la peine. Le Code comme Counter() n'a pas été commun dans le cadre idiomatique C# et/ou .NET. Vous pouvez facilement voir TCO pour C# comme une optimisation inutile ou prématurée.

avec le introduction de LINQ et d'autres choses, il semble que les développeurs C# et C# se déplacent dans des directions plus fonctionnelles. Donc, ce serait bien si utiliser la récursion n'était pas une chose aussi dangereuse à faire. Mais ma question est vraiment un plus technique.

manque quelque chose qui fait de TCO une mauvaise idée (ou une idée risquée) pour C#. Ou y a-t-il quelque chose qui rend la tâche particulièrement difficile? C'est vraiment ce que je suis l'espoir de comprendre. Toute idée?

EDIT: Merci pour l'information. Je voulais juste être clair sur le fait que je ne critique pas l'absence ou même l'exigence de cette fonctionnalité. Je ne suis pas très intéressé par la logique autour de la priorisation. Ma curiosité est de savoir quels sont les obstacles que je ne pourrais pas percevoir ou comprendre qui font que c'est une chose difficile, dangereuse, ou indésirable à faire.

peut-être qu'un contexte différent aidera à orienter la conversation...

Disons que j'allais implémenter mon propre langage de type C#sur le CLR. Pourquoi n'inclurais-je pas (autre que le coût d'opportunité) l'émission automatique et transparente de la queue.'instruction le cas échéant? Quels défis rencontrerais-je ou quelles contraintes introduirais-je en supportant cette fonctionnalité dans une langue très proche de C#?

Merci encore (et à l'avance) pour les réponses.

26
demandé sur Community 2011-08-17 20:17:55

2 réponses

vérifiez les liens suivants

Pourquoi ne pas .NET/C# optimiser la queue-appel de la récursivité? /491463#491463

http://social.msdn.microsoft.com/Forums/en-US/netfxtoolsdev/thread/67b6d908-8811-430f-bc84-0081f4393336?StatusCode=1

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=166013&wa=wsignin1.0

la déclaration suivante est MS official (Luke Hoban Visual C # Compiler Program Manager) et copié du dernier lien

Merci pour la suggestion. Nous avons envisagé d'envoyer des instructions d'appel de queue à un numéro de points dans le développement du compilateur C#. Cependant, il y a quelques questions subtiles qui nous ont poussés à éviter cela jusqu'à présent: 1) Il y a en fait un non-trivial de frais généraux coût à l'aide de l' .instruction de queue dans le CLR (il ne s'agit pas seulement d'une instruction de saut comme les appels de queue deviennent en fin de compte dans de nombreux environnements moins stricts tels que les environnements d'exécution de langage où les appels de queue sont fortement optimisés). 2) Il y a peu de véritables méthodes C# où il serait légal d'émettre des appels de queue (autres langues encourager les modèles de codage qui ont plus de récursion de la queue, et beaucoup qui dépendent fortement sur la queue l'optimisation des appels effectue en fait une réécriture globale (telle que le passage de la suite transformations) pour augmenter la quantité de recursion de la queue). 3) en partie à cause de 2), cas où c # méthodes empile le débordement dû à une profonde récursion qui aurait dû réussir sont assez rares.

Tout ce que dit, nous continuons à regarder cela, et nous pouvons, dans une future version de l' compilateur trouver quelques modèles où il est logique d'émettre .instructions générales.

12
répondu Yahia 2017-05-23 11:45:17

bonne question. Je n'ai pas de réponse concrète, mais j'ai des idées qui pourraient vous intéresser. (On m'a déjà dit que je ne devrais pas poster des choses comme des réponses, mais bon... La réponse de Yahia semble être la plus définitive que vous ayez, mais je vais aussi envoyer un ping à Eric Lippert pour voir s'il veut intervenir.

partie du post de blog Hans lié à dans les commentaires couvrira probablement certains de ses pensées, mais je suis sûr qu'il y a plus:

on me demande" pourquoi C# n'implémente pas la fonctionnalité X?"tout le temps. La réponse est toujours la même: parce que personne n'a jamais conçu, spécifié, implémenté, testé, documenté et expédié cette fonctionnalité. Tous ces six éléments sont nécessaires pour qu'une fonctionnalité se concrétise. Tous coûtent énormément de temps, d'efforts et d'argent. Les fonctionnalités ne sont pas bon marché, et nous essayons très dur de s'assurer que nous sommes seulement l'expédition ces fonctionnalités qui offrent les meilleurs avantages possibles à nos utilisateurs étant donné nos budgets limités en temps, en efforts et en argent.


maintenant retour à mes propres pensées:

je soupçonne que ce n'est tout simplement jamais une priorité, mais il ya une bonne raison de ne pas être trop gung-ho à ce sujet: si les développeurs vont compter sur elle, il doit être garanti . Vous avez écrit:

Donc, ce serait bien si l'utilisation de la récursivité n'était pas une mauvaise chose à faire. Mais ma question est vraiment un plus technique.

Maintenant, regardez l'article de blog expliquant quand les optimisations d'appel de queue sont appliquées . C'était en 2007, et il explicitement déclare:

Veuillez noter que ces énoncés s'appliquent aux ECE comme ils l'étaient lorsque Grant et la Fei ont examiné les base de code, et sont susceptibles de changer à sa guise. Vous ne devez pas prendre de dépendances sur ce comportement. Utiliser cette information pour votre propre divertissement.

il y a alors une longue liste de conditions qui sont nécessaires avant que les appels de queue seront appliqués. Beaucoup de ceux-ci ne sont pas anodins à deviner du point de vue d'un codeur.

vous avez absolument raison que ce serait bien si l'utilisation de la récursion était sûre dans certaines circonstances - mais je je crois que ce n'est le cas que s'il pouvait être garanti pour être sûr. Si c'était sûr dans 95% des cas, mais que les 5% étaient difficiles à prédire, nous aurions un tas de bugs "travaille sur ma machine".

ce que j'aimerais, c'est que le langage C# me permette d'énoncer une" exigence d'optimisation de l'appel de queue. Le compilateur pourrait alors vérifier qu'il est correct, et idéalement fournir plus qu'un simple conseil au JIT que ce comportement est requis. En gros, si je dois recommencer de façon incontrôlée, j'ai intérêt à savoir que ça va marcher. Pouvez-vous imaginer si la collecte des ordures n'était activée que dans certaines configurations? Eek!

donc +1 à l'idée de la récursion de la queue étant un concept utile, mais je pense que nous avons besoin de plus de soutien de la langue, le JIT et le compilateur avant qu'il ne puisse vraiment être considéré comme "sûr".

7
répondu Jon Skeet 2011-08-17 16:31:03