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();
}
}
我不明白为什么2000次调用'DoSomeWork'会调用800万个匿名方法关闭(甚至1会导致数千个)。
作为修复,我只是在不使用LINQ的情况下重写了该语句,从而消除了对闭包的需求并使性能提高了10倍。
如果有人有一些他们想分享的理论,我仍然想明白为什么会出现这种情况。
答案 0 :(得分: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; } }
现在,我们可以放弃原来的“//&lt; ---只调用1个匿名方法关闭”。注释,因为.Any()
实际使用了 no 匿名方法闭包 - 只检查列表是否包含内容:不需要闭包。
现在;让我们手动重写闭包,以显示编译器中发生的事情:
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
创建了一个DoSomeWork
,它直接映射到唯一捕获的变量(item
)在方法级别的作用域。 谓词(ctx.Predicate
)获得一次(仅限),但会为bList
中的每个项目调用。确实,2000 * 4000是对方法的8M调用;但是,对方法的8M调用不一定很慢。
然而!我认为最大的问题是你正在创建一个新的列表来检查是否存在。你不需要那个。您可以通过提前移动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();
}
但请注意,Any
和foreach
之间的此更改不是的关键差异; 严重的区别在于我删除了ToList()
和“继续阅读,即使您已经找到匹配”。 Any(predicate)
用法更加简洁,易于阅读等。这通常不是性能问题,我怀疑它是否在这里。
答案 1 :(得分:3)
在第
行var query = bList.where(x => x.prop1 == item.A && x.prop2 = item.B).toList();
bList有4000个元素,x => x.prop1 == item.A && x.prop2 = item.B
将被调用4000次。如果您希望懒惰地评估.Any()
,请移除.ToList()
。