Perché il compilatore C # impazzisce con questa query LINQ annidata?


97

Prova a compilare il seguente codice e scoprirai che il compilatore richiede> 3 GB di RAM (tutta la memoria libera sulla mia macchina) e un tempo molto lungo per la compilazione (in realtà ottengo un'eccezione IO dopo 10 minuti).

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

Qualcuno può spiegare questo curioso comportamento?

Versione CS: compilatore Microsoft (R) Visual C # versione 4.0.30319.17929
Nome sistema operativo: Microsoft Windows 7 Ultimate
Versione sistema operativo: 6.1.7601 Service Pack 1 Build 7601

Utilizzo della memoria


5
Ottima scelta! Ho appena incollato il codice in Visual Studio e ha consumato tutti i 4 Gb consentiti a un processo a 32 bit e poi si è bloccato (2013 Ultimate su Windows 8.1).
satnhak

2
Aggiungi questo codice a una base di codice condivisa (utilizzando il blocco note) e osserva le macchine dei tuoi colleghi bloccarsi.
usr

3
Sembra una buona cosa segnalare su Microsoft Connect e al team di Roslyn se il loro compilatore mostra lo stesso comportamento.
Trillian

3
Credo di aver sentito Eric Lippert dire da qualche parte (anche se non ricordo dove) che annidare i lambda troppo spesso con l'inferenza del tipo può causare al compilatore alcuni fastidiosi mal di testa. Non riesco a pensare dove l'ho visto, quindi non posso citare un riferimento. Si spera che l'uomo stesso possa vedere questo e commentare ...
Chris

2
Ben fatto, taglialo e potresti avere una bella risposta per questo: Crash your favorite compiler
Nathan Cooper

Risposte:


40

Credo che sia correlato all'inferenza del tipo e / o alla generazione di lambda (quando l'inferenza del tipo deve andare nella direzione opposta al normale), combinata con la risoluzione del sovraccarico. Sfortunatamente, fornire solo i parametri del tipo non aiuta la situazione (dove presumibilmente deve ancora eseguire il controllo del tipo).

Il codice seguente, che dovrebbe logicamente essere il codice equivalente del tuo, dopo che i lambda sono stati analizzati, viene compilato senza problemi:

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

Credo che Eric Lippert abbia pubblicato prima che l'inferenza del tipo sia uno dei posti nel compilatore C # in cui (alcuni problemi) potrebbero costringere il compilatore a provare a risolvere un problema NP-Complete e la sua unica vera strategia (come qui) è la forza bruta. Se riesco a trovare i riferimenti pertinenti, li aggiungerò qui.


Il miglior riferimento che posso trovare è qui dove Eric sta discutendo del fatto che è il lavoro di risoluzione dell'overload che causa il costo reale: ricorda, Enumerable.Sum ha 10 overload che accettano un metodo lambda /.


1
Quindi, fondamentalmente, il compilatore si fa strada attraverso le 10^ncombinazioni (dov'è nla quantità di metodi concatenati). Sembra ragionevole (come spiegazione, cioè).
DarkWanderer

1
@DarkWanderer:that^numberofpossibletypes
leppie

@Damien_The_Unbeliever, capisco il tuo pensiero, sembra davvero ragionevole (dal modo in cui il codice non si compila )
Eugene D. Gubenkov

15
La tua analisi qui è corretta. Ogni lambda introduce un fattore di dieci possibili sovraccarichi e tutte le combinazioni devono essere controllate. Ho preso in considerazione l'aggiunta di codice che si è salvato quando il numero di combinazioni è aumentato, ma non è mai stato implementato.
Eric Lippert

2
@EricLippert È ora di una richiesta pull! : D
Rob H
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.