4

Eqatec は、プログラムに単純な LINQ 'Where' ステートメントを含むメソッドが呼び出されるたびに、数千の匿名メソッド クロージャーが呼び出されることを示しています。疑似コードの例:

Class1
{
    //foo and bar are both EF model classes
    List<foo> aList; // n = 2000
    List<bar> bList; // n = ~4000

    void aMethod() 
    {  
        foreach (var item in aList)
        {
            Class2.DoSomeWork(item, bList);
        }
    }
}

Class2
{
    static void DoSomeWork(foo item, List<bar> bList)
    {
     var query = bList.where(x => x.prop1 == item.A && x.prop2 = item.B).toList(); // <--- Calls thousands of anonymous method closures each method call.

     if (query.any()) <--- Calls only 1 anonymous method closure.
        DoSomethingElse(); 
    } 
}

「DoSomeWork」への 2,000 回の呼び出しで約 800 万回の匿名メソッド クロージャが呼び出された理由がわかりません (1 回でも数千回発生します)。

修正として、LINQ を使用せずにステートメントを書き直しただけで、クロージャが不要になり、パフォーマンスが 10 倍向上しました。

誰かが共有したい理論を持っている場合、なぜこれが最初に起こったのかを理解したい.

4

2 に答える 2

4

8Mは、作成されたクロージャインスタンスの数ではなく、クロージャクラスでメソッドが実行された回数を指していると思います。まず、コードをコンパイルしてみましょう。

class Class2
{
    public static void DoSomeWork(foo item, List<bar> bList)
    {
        var query = bList.Where(x => x.prop1 == item.A && x.prop2 == item.B)
                         .ToList();

        if (query.Any())
            DoSomethingElse();
    }
    static void DoSomethingElse() { }
}
class foo { public int A { get; set; } public int B { get; set; } }
class bar { public int prop1 { get; set; } public int prop2 { get; set; } }

これで、元の「//<---匿名メソッドクロージャを1つだけ呼び出す」を破棄できます。コメント。実際には、匿名メソッドのクロージャは使用されないため.Any()、リストに内容があるかどうかをチェックするだけです。クロージャは必要ありません。

今; コンパイラで何が起こっているかを示すために、クロージャを手動で書き直してみましょう。

class Class2
{
    class ClosureClass
    {
        public foo item; // yes I'm a public field
        public bool Predicate(bar x)
        {
            return x.prop1 == item.A && x.prop2 == item.B;
        }
    }
    public static void DoSomeWork(foo item, List<bar> bList)
    {
        var ctx = new ClosureClass { item = item };
        var query = bList.Where(ctx.Predicate).ToList();

        if (query.Any()) {
            DoSomethingElse();
        }
    }
    static void DoSomethingElse() { }
}

ClosureClassごとに1が作成されていることがわかります。これは、キャプチャされた変数( )のみがメソッドレベルでスコープさDoSomeWorkれる方法に直接マップされます。述語()は1回だけ取得itemますが、内のすべての項目に対して呼び出されます。したがって、実際、2000*4000はメソッドへの800万回の呼び出しです。ただし、メソッドへの8Mの呼び出しは必ずしも遅いとは限りません。ctx.PredicatebList

でも!最大の問題は、存在を確認するためだけに新しいリストを作成していることだと思います。あなたはそれを必要としません。以前に移動することで、コードをはるかに効率的にすることができます。Any

if (bList.Any(x => x.prop1 == item.A && x.prop2 == item.B)) {
    DoSomethingElse();
}

これにより、一致が見つかるまで十分な回数だけ述語が呼び出されるようになりました。これは、すべての述語よりも少ないと予想されます。また、リストが不必要にいっぱいになることもありません。

今; はい、これを手動で行う方が少し効率的です。

bool haveMatch = false;
foreach(var x in bList) {
    if(x.prop1 == item.A && x.prop2 == item.B) {
        haveMatch = true;
        break;
    }
}
if(haveMatch) {
    DoSomethingElse();
}

ただし、との間のこの変更は重大な違いAnyforeachないことに注意してください。重要な違いは、とを削除したToList()ことです。「一致するものが既に見つかった場合でも、読み続けてください」。使用Any(predicate)法ははるかに簡潔で読みやすいなどです。これは通常、パフォーマンスの問題ではなく、ここにもあるとは思えません。

于 2012-08-14T07:12:45.520 に答える
3

ラインで

var query = bList.where(x => x.prop1 == item.A && x.prop2 = item.B).toList();

4000 個の要素を持つ bList では、 x => x.prop1 == item.A && x.prop2 = item.B4000 回呼び出されます。.Any()を遅延評価したい場合は、 を削除して.ToList()ください。

于 2012-08-14T06:53:19.650 に答える