Mengapa kompilator C # menjadi gila pada kueri LINQ bersarang ini?


97

Cobalah untuk mengkompilasi kode berikut dan Anda akan menemukan bahwa kompiler membutuhkan> 3 GB RAM (semua memori gratis di mesin saya) dan waktu yang sangat lama untuk mengkompilasi (sebenarnya saya mendapatkan pengecualian IO setelah 10 menit).

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

Adakah yang bisa menjelaskan perilaku penasaran ini?

Versi CS: Microsoft (R) Visual C # Compiler versi 4.0.30319.17929
Nama OS: Microsoft Windows 7 Ultimate
Versi OS: 6.1.7601 Paket Layanan 1 Build 7601

Penggunaan Memori


5
Panggilan yang bagus! Saya baru saja menempelkan kode ke studio visual dan menghabiskan semua 4Gb yang diizinkan untuk proses 32-bit dan kemudian macet (2013 Ultimate pada Windows 8.1).
satnhak

2
Tambahkan kode ini ke basis kode bersama (menggunakan notepad) dan lihat mesin rekan kerja Anda mogok.
usr

3
Kedengarannya bagus untuk melaporkan di Microsoft Connect, dan ke tim Roslyn jika kompiler mereka menunjukkan perilaku yang sama.
Trillian

3
Saya percaya saya pernah mendengar Eric Lippert mengatakan di suatu tempat (meskipun saya tidak ingat di mana) bahwa lambda bersarang terlalu sering dengan inferensi tipe dapat menyebabkan kompiler sakit kepala yang parah. Saya tidak bisa memikirkan di mana saya melihatnya jadi tidak bisa mengutip referensi. Mudah-mudahan pria itu sendiri dapat melihat ini dan berkomentar ...
Chris

2
Bagus, rapikan dan Anda mungkin memiliki jawaban yang bagus untuk ini: Hancurkan kompiler favorit Anda
Nathan Cooper

Jawaban:


40

Saya percaya bahwa ini terkait dengan inferensi tipe dan / atau generasi lambda (ketika inferensi tipe harus berlawanan arah dengan normal), dikombinasikan dengan resolusi yang berlebihan. Sayangnya, hanya menyediakan parameter tipe tidak membantu situasi (di mana mungkin masih harus melakukan pemeriksaan tipe).

Kode berikut, yang secara logis harus merupakan kode yang setara dari Anda, setelah lambda dianalisis, dikompilasi tanpa masalah:

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

Saya percaya Eric Lippert telah memposting sebelumnya bahwa inferensi tipe adalah salah satu tempat di kompiler C # di mana (masalah tertentu) dapat memaksa kompiler untuk mencoba memecahkan masalah NP-Complete dan satu-satunya strategi nyata (seperti di sini) adalah kekerasan. Jika saya dapat menemukan referensi yang relevan, saya akan menambahkannya di sini.


Referensi terbaik yang dapat saya temukan adalah di sini di mana Eric membahas fakta bahwa itu adalah pekerjaan resolusi kelebihan beban yang menyebabkan biaya sebenarnya - ingat, Enumerable.Sum memiliki 10 kelebihan beban yang menerima lambda / metode.


1
Jadi, pada dasarnya, kompilator memaksa jalannya melalui 10^nkombinasi (di mana njumlah metode yang dirantai). Kedengarannya masuk akal (sebagai penjelasannya).
DarkWanderer

1
@DarkWanderer:that^numberofpossibletypes
leppie

@Damien_The_Unbeliever, saya memahami pemikiran Anda, jahitannya sangat masuk akal (dengan cara kode tidak dapat dikompilasi )
Eugene D. Gubenkov

15
Analisis Anda di sini benar. Setiap lambda memasukkan faktor sepuluh kemungkinan kelebihan beban, dan semua kombinasi harus diperiksa. Saya mempertimbangkan untuk menambahkan kode yang ditebus ketika jumlah kombinasi menjadi besar tetapi tidak pernah akhirnya menerapkannya.
Eric Lippert

2
@Waktu untuk permintaan tarik! : D
Rob H
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.