使用LINQ时,谓词的顺序是否重要?

9

我明白在不同的顺序执行操作会产生不同的性能,比如以下慢查询之间的差异:

List<TestItem> slowResults = items.OrderBy(item => item.StringItem)
                                  .Where(item => item.IntItem == 100)
                                  .ToList();

还有这个更快的:

List<TestItem> fastResults = items.Where(item => item.IntItem == 100)
                                  .OrderBy(item => item.StringItem)
                                  .ToList();

但那不是我的问题:

我的问题是关于短路计算与LINQ谓词的性能相关的。当我使用Where子句时,就像在这个例子中:

List<TestItem> results = items.Where(item => item.Item1 == 12 &&
                                             item.Item2 != null &&
                                             item.Item2.SubItem == 65 &&
                                             item.Item3.Equals(anotherThingy))
                              .ToList();

参数的顺序有影响吗?例如,我会认为首先执行.Equals将导致查询总体变慢,因为Item1 == 12整数评估是更快的操作?
如果顺序很重要,它有多重要?当然,调用类似.Equals的方法可能会比仅比较几个整数导致更大的减速,但与LINQ操作的“缓慢”相比,它是否是一个相对较小的性能损失?由于LINQ进行了大量的方法调用,像.Equals这样的东西真的会很重要吗?除非被覆盖,否则它将执行本机框架代码,是吗?另一方面,标准的MSIL方法调用会慢得多吗?
此外,此查询上是否有任何其他编译器优化可能使其在幕后加速?
感谢您的想法和澄清! Brett

我在考虑 LINQ to Objects,但我想对于 LINQ to SQL 这个问题更为重要。 - Brett
2
虽然我不了解LINQ,但如果它像其他短路语言一样,顺序是很重要的,因为如果要评估的表达式变得肯定为真或肯定为假,那么其余的谓词可以安全地丢弃。例如,在表达式(1 == 1 || x == 3)中,x==3永远不会被评估,因为无论结果如何,表达式都将为真。类似的简单示例可能是(1==0 && x == 3),在发现1==0为假后,它就会停止执行,因为表达式不可能为真。如果这不是您想要的内容,对不起! - prelic
这正是我所想的,@prelic!但我也在想是否有任何编译器优化正在进行,或者包括方法调用的查询是否可以被优化? - Brett
@Brett 这只是一个猜测,但我认为没有数据库查询优化,因为如果有人外部连接并更改了数据库会发生什么?我猜每个查询即使之前已经执行过,也会再次执行以保持事务保证,而自包含的应用程序可能能够做出更多的假设,但就像我说的那样,这纯粹是一个没有事实依据的猜测。查询本身可以被优化为更好的形式(更快的)查询,但我怀疑幕后是否有任何缓存或记忆化。 - prelic
1个回答

13

对于不同的LINQ提供程序,答案会有所不同。特别是,对于LINQ到对象和LINQ到实体来说,情况大不相同。

在LINQ to Objects中,Where操作符接受函数Func<TSource, bool>作为过滤器。Func<,>是一个委托,因此就本讨论而言,您可以将其视为函数指针。在LINQ to Objects中,您的查询等效于以下内容:

static void Main() {
    List<TestItem> results = items.Where(MyFilter).ToList(); 

static boolean MyFilter(TestItem item) {
    return item.Item1 == 12 && 
        item.Item2 != null && 
        item.Item2.SubItem == 65 && 
        item.Item3.Equals(anotherThingy)
}
注意的主要事项是,MyFilter是一个普通的C#方法,因此普通的C#规则适用,包括&&的短路行为。 因此,条件将按照您编写它们的顺序进行评估。 LINQ对对象可以在不同的输入元素上调用MyFilter,但无法更改MyFilter的功能。
在LINQ to Entities和LINQ to SQL中,Where运算符接受过滤器作为Expression<Func<TSource,bool>>。 现在,过滤器作为描述表达式的数据结构传递给Where运算符。 在这种情况下,LINQ提供程序将查看数据结构(“表达式树”),由LINQ提供程序决定如何解释它。
在LINQ to Entities和LINQ to SQL案例中,表达式树将被转换为SQL。 然后,由数据库服务器决定如何执行查询。 服务器肯定可以重新排序条件,并可能进行更大的优化。 例如,如果SQL表中包含在条件中引用的列之一的索引,则服务器可以选择使用该索引并避免甚至不查看不匹配该特定条件部分的行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接