Por que o compilador C # enlouquece com essa consulta LINQ aninhada?

97

Tente compilar o código a seguir e você descobrirá que o compilador leva> 3 GB de RAM (toda a memória livre em minha máquina) e muito tempo para compilar (na verdade, recebo a exceção de IO após 10 minutos).

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)))))));
    }
}

Alguém pode explicar esse comportamento curioso?

Versão CS: Microsoft (R) Visual C # Compiler versão 4.0.30319.17929
Nome do SO: Microsoft Windows 7 Ultimate
Versão do sistema operacional: 6.1.7601 Service Pack 1 Build 7601

Uso de memória

Eugene D. Gubenkov
fonte
5
Boa decisão! Acabei de colar o código no Visual Studio e ele consumiu todos os 4Gb permitidos para um processo de 32 bits e, em seguida, travou (2013 Ultimate no Windows 8.1).
satnhak
2
Adicione este código a uma base de código compartilhada (usando o bloco de notas) e observe as máquinas de seus colegas de trabalho travarem.
usr
3
Parece uma boa coisa relatar no Microsoft Connect e para a equipe Roslyn se o seu compilador exibir o mesmo comportamento.
Trillian de
3
Acredito ter ouvido Eric Lippert dizer em algum lugar (embora não me lembre onde) que aninhar lambdas com muita frequência com inferência de tipo pode causar ao compilador algumas dores de cabeça desagradáveis. Não consigo imaginar onde o vi, então não posso citar uma referência. Espero que o próprio homem veja isso e comente ...
Chris,
2
Muito bem, reduza-o e você terá uma boa resposta para isso: Destrua seu compilador favorito
Nathan Cooper

Respostas:

40

Acredito que esteja relacionado à inferência de tipo e / ou geração lambda (quando a inferência de tipo tem que ir na direção oposta ao normal), combinada com resolução de sobrecarga. Infelizmente, apenas fornecer os parâmetros de tipo não ajuda a situação (onde presumivelmente ainda terá que realizar a verificação de tipo).

O código a seguir, que deve ser logicamente o código equivalente ao seu, após a análise dos lambdas, é compilado sem problemas:

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;
}

Eu acredito que Eric Lippert postou antes que a inferência de tipo é um dos lugares no compilador C # onde (certos problemas) podem forçar o compilador a tentar resolver um problema NP-Completo e sua única estratégia real (como aqui) é a força bruta. Se eu conseguir encontrar as referências relevantes, vou adicioná-las aqui.


A melhor referência que posso encontrar é aqui onde Eric está discutindo o fato de que é o trabalho de resolução de sobrecarga que causa o custo real - lembre-se, Enumerable.Sum tem 10 sobrecargas que aceitam um lambda / método.

Damien_The_Unbeliever
fonte
1
Então, basicamente, o compilador força bruta seu caminho através das 10^ncombinações (onde nestá a quantidade de métodos encadeados). Parece razoável (como uma explicação, isso é).
DarkWanderer de
1
@DarkWanderer:that^numberofpossibletypes
leppie
@Damien_The_Unbeliever, eu entendo seu pensamento, parece muito razoável (pela forma como o código não compila )
Eugene D. Gubenkov
15
Sua análise aqui está correta. Cada lambda apresenta um fator de dez sobrecargas possíveis e todas as combinações devem ser verificadas. Considerei adicionar código que resgatava quando o número de combinações aumentava, mas nunca acabei implementando.
Eric Lippert,
2
@EricLippert É hora de uma solicitação de pull! : D
Rob H de