なぜC#コンパイラーはこのネストされたLINQクエリで狂ってしまうのですか?


97

次のコードをコンパイルしてみてください。コンパイラが3 GBを超えるRAM(私のマシンのすべての空きメモリ)とコンパイルに非常に長い時間がかかることがわかります(実際、10分後にIO例外が発生します)。

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

誰かがこの奇妙な行動を説明できますか?

CSバージョン:Microsoft(R)Visual C#コンパイラバージョン4.0.30319.17929
OS名:Microsoft Windows 7 Ultimate
OSバージョン:6.1.7601 Service Pack 1 Build 7601

メモリ使用量


5
よかった!コードをビジュアルスタジオに貼り付けたところ、32ビットプロセスが許可されていたすべての4Gbが消費されてクラッシュしました(Windows 8.1では2013 Ultimate)。
satnhak 2014

2
このコードを共有コードベース(メモ帳を使用)に追加し、同僚のマシンのクラッシュを監視します。
usr 14

3
Microsoft Connect、およびコンパイラーが同じ動作を示す場合はRoslynチームに報告するのは良いことのように思えます。
Trillian 2014

3
Eric Lippertがどこかで(どこかは思い出せませんが)ラムダを型推論でネストしすぎるとコンパイラーに厄介な問題が発生する可能性があると言っているのを聞いたと思います。どこで見たのかわからないので参考にできない。うまくいけば、男自身がこれを見てコメントするかもしれません...
クリス

2
よくやった、それを切り詰めて、あなたはこれに対する素晴らしい答えを持っているかもしれません:お気に入りのコンパイラをクラッシュさせてください
Nathan Cooper 14

回答:


40

これは、型の推論および/またはラムダの生成(型の推論が通常とは逆方向に進む必要がある場合)に関連し、過負荷の解決と組み合わせられていると思います。残念ながら、型パラメーターを指定するだけでは状況が改善されません(おそらく型チェックを実行する必要がある場合)。

次のコードは、ラムダが分析された後、論理的には同等のコードになるはずですが、問題なくコンパイルされます。

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

Eric Lippertが以前に投稿したと思いますが、その型推論はC#コンパイラーの(特定の問題)コンパイラーにNP-Complete問題の解決を強制する可能性がある場所の1つであり、(ここにあるように)その唯一の実際の戦略はブルートフォースです。関連する参考文献が見つかれば、ここに追加します。


私は見つけることができる最高の参照があり、ここでエリックのは、それが本当のコストが発生するオーバーロードの解決の仕事だということを議論する場所-覚えて、Enumerable.Sumはラムダ/メソッドを受け入れる10のオーバーロードがあります。


1
したがって、基本的には、コンパイラーは10^n組み合わせによってブルートフォースを実行します(nチェーンされるメソッドの量はここにあります)。合理的に聞こえます(説明として)。
DarkWanderer 2014

1
@DarkWanderer:that^numberofpossibletypes
leppie 2014

@Damien_The_Unbeliever、私はあなたの考えを理解しています、継ぎ目は本当に合理的です(ところで、コードはコンパイルされません
Eugene D.Gubenkov 14

15
ここでのあなたの分析は正しいです。各ラムダは10倍の可能性のある過負荷を導入し、すべての組み合わせを確認する必要があります。私は、組み合わせの数が多くなったときに救済されたコードを追加することを検討しましたが、それを実装することにはなりませんでした。
Eric Lippert 2014

2
@EricLippertプルリクエストの時間!:D
Rob H
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.