Dlaczego kompilator C # szaleje w tej zagnieżdżonej kwerendzie LINQ?

97

Spróbuj skompilować następujący kod, a zobaczysz, że kompilator zajmuje> 3 GB pamięci RAM (cała wolna pamięć na moim komputerze) i bardzo długi czas na kompilację (w rzeczywistości otrzymuję wyjątek IO po 10 minutach).

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

Czy ktoś może wyjaśnić to dziwne zachowanie?

Wersja CS: kompilator Microsoft (R) Visual C # w wersji 4.0.30319.17929
Nazwa systemu operacyjnego: Microsoft Windows 7 Ultimate
Wersja systemu operacyjnego: 6.1.7601 z dodatkiem Service Pack 1, kompilacja 7601

Zużycie pamięci

Eugene D. Gubenkov
źródło
5
Dobra decyzja! Właśnie wkleiłem kod do programu Visual Studio i zużyłem całe 4 Gb, na które pozwala proces 32-bitowy, a następnie zawiesiłem się (2013 Ultimate w systemie Windows 8.1).
satnhak
2
Dodaj ten kod do udostępnionej bazy kodu (za pomocą notatnika) i obserwuj awarię maszyn współpracowników.
usr
3
Wydaje się, że dobrze jest zgłosić w Microsoft Connect i zespołowi Roslyn, jeśli ich kompilator wykazuje takie samo zachowanie.
Trillian,
3
Wydaje mi się, że słyszałem gdzieś Eric Lippert (chociaż nie pamiętam gdzie), że zagnieżdżanie lambd zbyt często z wnioskiem o typie może powodować nieprzyjemne bóle głowy. Nie mogę pomyśleć, gdzie to widziałem, więc nie mogę przytoczyć odniesienia. Miejmy nadzieję, że on sam to zobaczy i skomentuje ...
Chris
2
Dobra robota, skróć to i możesz mieć na to dobrą odpowiedź: Rozbij swój ulubiony kompilator
Nathana Coopera

Odpowiedzi:

40

Uważam, że jest to związane z wnioskiem o typie i / lub generowaniem lambda (gdy wnioskowanie o typie musi iść w przeciwnym kierunku do normalnego), w połączeniu z rozwiązywaniem przeciążenia. Niestety, samo podanie parametrów typu nie pomaga w sytuacji (w której prawdopodobnie nadal musi wykonać sprawdzenie typu).

Poniższy kod, który logicznie powinien być równoważnym kodem z twojego, po przeanalizowaniu lambd, kompiluje się bez problemu:

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

Uważam, że Eric Lippert opublikował wcześniej, że wnioskowanie o typie jest jednym z miejsc w kompilatorze C #, w którym (pewne problemy) mogą zmusić kompilator do próby rozwiązania problemu NP-Complete, a jego jedyną realną strategią (jak tutaj) jest brutalna siła. Jeśli znajdę odpowiednie referencje, dodam je tutaj.


Najlepsze odniesienie, jakie mogę znaleźć, jest tutaj, gdzie Eric omawia fakt, że to praca nad rozwiązywaniem przeciążenia powoduje rzeczywisty koszt - pamiętaj, Enumerable. ma 10 przeciążeń, które akceptują lambda / metodę.

Damien_The_Unbeliever
źródło
1
Tak więc kompilator brutalnie przedostaje się przez 10^nkombinacje (gdzie njest liczba połączonych metod). Brzmi rozsądnie (to znaczy jako wyjaśnienie).
DarkWanderer
1
@DarkWanderer:that^numberofpossibletypes
leppie
@Damien_The_Unbeliever, rozumiem twoje myślenie, wydaje się naprawdę rozsądne (tak przy okazji, kod się nie kompiluje )
Eugene D. Gubenkov
15
Twoja analiza tutaj jest poprawna. Każda lambda wprowadza współczynnik dziesięciu możliwych przeciążeń i należy sprawdzić wszystkie kombinacje. Rozważałem dodanie kodu, który wyskoczył, gdy liczba kombinacji stała się duża, ale nigdy go nie zaimplementowałem.
Eric Lippert,
2
@EricLippert Czas na żądanie ściągnięcia! : D
Rob H