Pourquoi le compilateur C # devient-il fou sur cette requête LINQ imbriquée?

97

Essayez de compiler le code suivant et vous constaterez que le compilateur prend> 3 Go de RAM (toute la mémoire libre sur ma machine) et très longtemps pour compiler (en fait, j'obtiens une exception IO après 10 minutes).

using System;
using System.Linq;

public class Test
{
    public static void Main()
    {
        Enumerable.Range(0, 1).Sum(a =>
        Enumerable.Range(0, 1).Sum(b =>
        Enumerable.Range(0, 1).Sum(c =>
        Enumerable.Range(0, 1).Sum(d =>
        Enumerable.Range(0, 1).Sum(e =>
        Enumerable.Range(0, 1).Sum(f =>
        Enumerable.Range(0, 1).Count(g => true)))))));
    }
}

Quelqu'un peut-il expliquer ce comportement curieux?

Version CS: compilateur Microsoft (R) Visual C # version 4.0.30319.17929
Nom du système d'exploitation: Microsoft Windows 7 Ultimate
Version du système d'exploitation: 6.1.7601 Service Pack 1 Build 7601

Utilisation de la mémoire

Eugene D. Gubenkov
la source
5
Bon appel! Je viens de coller le code dans Visual Studio et il a consommé tous les 4 Go qu'un processus 32 bits est autorisé, puis s'est écrasé (2013 Ultimate sur Windows 8.1).
satnhak
2
Ajoutez ce code à une base de code partagée (en utilisant le bloc-notes) et regardez les machines de vos collègues planter.
usr
3
Cela semble être une bonne chose à signaler sur Microsoft Connect et à l'équipe de Roslyn si leur compilateur présente le même comportement.
Trillian
3
Je crois avoir entendu Eric Lippert dire quelque part (bien que je ne me souvienne pas où) que la nidification de lambdas trop souvent avec l'inférence de type peut causer des maux de tête au compilateur. Je ne peux pas penser où je l'ai vu, donc je ne peux pas citer de référence. J'espère que l'homme lui-même pourra le voir et commenter ...
Chris
2
Bien joué, réduisez-le et vous aurez peut-être une bonne réponse à cela: Crash votre compilateur préféré
Nathan Cooper

Réponses:

40

Je crois que cela est lié à l'inférence de type et / ou à la génération lambda (lorsque l'inférence de type doit aller dans la direction opposée à la normale), combinée à la résolution de surcharge. Malheureusement, le simple fait de fournir les paramètres de type n'aide pas la situation (où il doit probablement encore effectuer la vérification de type).

Le code suivant, qui devrait logiquement être le code équivalent du vôtre, une fois que les lambdas ont été analysés, se compile sans problème:

static void Main()
{
    var x = Enumerable.Range(0, 1).Sum(a);
}

private static int a(int a)
{
    return Enumerable.Range(0, 1).Sum(b);
}
private static int b(int b)
{
    return Enumerable.Range(0, 1).Sum(c);
}
private static int c(int c)
{
    return Enumerable.Range(0, 1).Sum(d);
}
private static int d(int d)
{
    return Enumerable.Range(0, 1).Sum(e);
}
private static int e(int e)
{
    return Enumerable.Range(0, 1).Sum(f);
}
private static int f(int f)
{
    return Enumerable.Range(0, 1).Count(g);
}
private static bool g(int g)
{
    return true;
}

Je crois qu'Eric Lippert a déjà posté que l'inférence de type est l'un des endroits du compilateur C # où (certains problèmes) peuvent forcer le compilateur à essayer de résoudre un problème NP-Complete et sa seule vraie stratégie (comme ici) est la force brute. Si je peux trouver les références pertinentes, je les ajouterai ici.


La meilleure référence que je puisse trouver est ici où Eric discute du fait que c'est le travail de résolution de surcharge qui cause le coût réel - rappelez-vous, Enumerable.Sum a 10 surcharges qui acceptent une méthode lambda /.

Damien_The_Unbeliever
la source
1
Donc, fondamentalement, le compilateur se fraye un chemin à travers les 10^ncombinaisons (où nest la quantité de méthodes enchaînées). Cela semble raisonnable (à titre d'explication, c'est-à-dire).
DarkWanderer
1
@DarkWanderer:that^numberofpossibletypes
leppie
@Damien_The_Unbeliever, je comprends votre pensée, les coutures sont vraiment raisonnables (d'ailleurs le code ne se compile pas )
Eugene D. Gubenkov
15
Votre analyse ici est correcte. Chaque lambda introduit un facteur de dix surcharges possibles, et toutes les combinaisons doivent être vérifiées. J'ai envisagé d'ajouter du code qui a renfloué lorsque le nombre de combinaisons est devenu important mais qui n'a jamais fini par l'implémenter.
Eric Lippert
2
@EricLippert C'est l'heure d'une pull request! : D
Rob H