简体   繁体   English

是什么导致C#中过多的匿名方法闭包(c__DisplayClass1)?

[英]What causes excessive anonymous method closures (c__DisplayClass1) in C#?

Eqatec shows several thousand anonymous method closures being called each time a method is called that includes a simple LINQ 'Where' statement in my program. Eqatec显示每次调用一个方法时调用的数千个匿名方法闭包,它在我的程序中包含一个简单的LINQ'Where'语句。 Pseudo code example: 伪代码示例:

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

I don't understand why 2,000 calls to 'DoSomeWork' called some 8 million anonymous method closures (even 1 causes several thousand). 我不明白为什么2000次调用'DoSomeWork'会调用800万个匿名方法关闭(甚至1个导致数千个)。

As a fix, I simply rewrote the statement without using LINQ which eliminated the need for closures and produced a 10x fold increase in performance. 作为修复,我只是在不使用LINQ的情况下重写了语句,从而消除了对闭包的需求并使性能提高了10倍。

I would still like to understand why this occurred in the first place if anyone has some theories they would like to share. 如果有人有一些他们想分享的理论,我仍然想知道为什么这首先发生了。

I think the 8M is referring to the number of times a method was executed on a closure class, and not the number of closure instances created. 我认为8M指的是在闭包类上执行方法的次数,而不是创建的闭包实例的数量。 Firstly, let's make the code compile: 首先,让我们编译代码:

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

Now, we can discard the original " // <--- Calls only 1 anonymous method closure." 现在,我们可以丢弃原始的“// <---只调用1个匿名方法闭包”。 comment, because actually no anonymous method closures are used by the .Any() - that just checks whether a list has contents: no closures required. 注释,因为.Any()实际上没有使用匿名方法闭包 - 只检查列表是否包含内容:不需要闭包。

Now; 现在; let's manually rewrite the closure to show what is happening in the compiler: 让我们手动重写闭包以显示编译器中发生的事情:

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() { }
}

You can see that 1 ClosureClass is created per DoSomeWork , which maps directly to how the only captured variable ( item ) is scoped at the method level. 可以看到,1个ClosureClass每创建DoSomeWork ,直接映射到如何唯一捕获变量( item )在方法级别的作用域。 The predicate ( ctx.Predicate ) is obtained once (only), but is invoked for every item in bList . 谓词ctx.Predicate )只获取一次 (仅),但是为bList每个项目调用。 So indeed, 2000 * 4000 is 8M calls to a method; 确实,2000 * 4000是对方法的8M调用; however, 8M calls to a method is not necessarily slow. 但是,对方法的8M调用不一定很慢。

However! 然而! I think the biggest problem is that you are creating a new list just to check for existence. 我认为最大的问题是你正在创建一个新的列表来检查是否存在。 You don't need that. 你不需要那个。 You can make your code much more efficient by moving the Any earlier: 你可以让你的代码有效通过移动Any更早版本:

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

This now only invokes the predicate enough times until a match is found, which we should anticipate to be less than all of them; 现在这只会调用谓词足够多次,直到找到一个匹配,我们应该预期它会比所有这些都少; it also doesn't fill a list unnecessarily. 它也没有不必要的填写清单。

Now; 现在; yes , it will be be a bit more efficient to do this manually, ie 是的 ,这将是多一点效率手动做到这一点,即

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

but note that this change between Any and foreach is not the critical difference; 但请注意, Anyforeach之间的这种变化并不是关键的区别; the critical difference is that I've removed the ToList() and the "keep reading, even if you've already found a match". 关键的区别是我已经删除了ToList()和“继续阅读,即使你已经找到匹配”。 The Any(predicate) usage is a lot more concise and is easy to read etc. It isn't typically a performance issue, and I doubt it is here either. Any(predicate)用法更简洁,易于阅读等。它通常不是性能问题,我怀疑它是否在这里。

In the line 在线

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

with bList having 4000 elements, x => x.prop1 == item.A && x.prop2 = item.B is going to be called 4000 times. bList有4000个元素, x => x.prop1 == item.A && x.prop2 = item.B将被调用4000次。 If you want the .Any() to be evaluated lazily, remove the .ToList() . 如果您希望.Any()进行评估懒惰,删除.ToList()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM