[英]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; 但请注意,
Any
和foreach
之间的这种变化并不是关键的区别; 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.