LINQ函数的顺序是否重要?

121

基本上,问题的意思是...在性能方面,LINQ函数的顺序是否重要?显然结果仍然必须相同...

例如:

myCollection.OrderBy(item => item.CreatedDate).Where(item => item.Code > 3);
myCollection.Where(item => item.Code > 3).OrderBy(item => item.CreatedDate);

两者都返回相同的结果,但LINQ顺序不同。我知道重新排序某些项将导致不同的结果,我不关心这些。我的主要关注点是是否知道在获得相同结果时,排序是否会影响性能。而且,不仅仅是对我做的这两个LINQ调用(OrderBy、Where),而是对任何LINQ调用。


提供程序的优化在更严谨的情况下尤为重要,例如 var query = myCollection.OrderBy(item => item.Code).Where(item => item.Code == 3); - Mark Hurd
1
@GibboK:在尝试“优化”您的LINQ查询时要小心(请参见下面的答案)。有时您实际上并没有优化任何内容。在尝试优化时最好使用分析器工具。 - myermian
7个回答

152

这将取决于所使用的LINQ提供程序。对于LINQ to Objects,这肯定会产生巨大的影响。假设我们实际上拥有:

var query = myCollection.OrderBy(item => item.CreatedDate)
                        .Where(item => item.Code > 3);

var result = query.Last();

这需要对整个集合进行排序,然后再进行筛选。如果我们有一百万个项目,只有一个项目的代码大于3,那么我们将浪费很多时间来排序结果,而这些结果最终会被抛弃。

与先进行过滤的操作相比较:

var query = myCollection.Where(item => item.Code > 3)
                        .OrderBy(item => item.CreatedDate);

var result = query.Last();

这次我们只订购筛选结果,对于“仅匹配筛选条件的单个项目”这种样本情况来说,这将更加高效 - 在时间和空间上都是如此。

这也可能会影响查询是否执行正确。考虑以下情况:

var query = myCollection.Where(item => item.Code != 0)
                        .OrderBy(item => 10 / item.Code);

var result = query.Last();

没问题 - 我们知道我们永远不会除以0。但是,如果我们在过滤之前进行排序,查询将会抛出异常。


2
@Jon Skeet,LINQ提供程序和函数的每个Big-O是否有文档记录?还是这仅仅是“每个表达式都独特于情况”的情况? - michael
1
@michael:文档并不是很清晰,但如果你阅读我的“Edulinq”博客系列,我认为我会详细地谈论它。 - Jon Skeet
3
@gdoron:说实话,你的意思并不是很清楚。听起来你可能想写一个新问题。请注意,Queryable根本不会尝试“解释”您的查询-它的工作只是为了保留您的查询,以便其他东西可以解释它。请注意LINQ to Objects甚至不使用表达式树。 - Jon Skeet
2
@gdoron: 关键是这是提供者的工作,而不是Queryable的工作。但使用Entity Framework时,这并不重要。然而,对于LINQ to Objects来说确实很重要。但是,无论如何,请提出另一个问题。 - Jon Skeet
1
为了避免未来的开发人员因为链接失效而不得不去谷歌搜索,这里提供一个可靠的链接:https://codeblog.jonskeet.uk/category/edulinq/ - Alex McMillan
显示剩余4条评论

17

可以。

但是,性能差异究竟是多少取决于LINQ提供程序如何评估底层表达式树。

例如,对于LINQ-to-XML,您的查询可能在第二次执行(WHERE子句在前)时更快,而对于LINQ-to-SQL,则在第一次执行时更快。

要精确地了解性能差异,您很可能需要对应用程序进行分析。然而,在这种情况下,过早进行优化通常是不值得的——您可能会发现,除了LINQ性能之外,其他问题更为重要。


5

对于你的特定示例,它可以影响性能。

第一个查询:您的OrderBy调用需要遍历整个源序列,包括那些Code小于或等于3的项目。然后Where子句还需要遍历整个排序序列。

第二个查询: Where调用限制了序列只包括Code大于3的项目。然后OrderBy调用仅需要遍历由Where调用返回的缩小序列。


3
在Linq-To-Objects中:
排序相对较慢,并且使用O(n)的内存。另一方面,Where相对快速并且使用恒定的内存。因此,首先进行Where操作将更快,对于大型集合来说可以显著提高效率。
减少内存压力也很重要,因为根据我的经验,在大对象堆上分配内存(以及它们的收集)比较昂贵。

1
显然,结果仍然必须相同...请注意,这实际上并不正确-特别是以下两行将给出不同的结果(对于大多数提供商/数据集):
myCollection.OrderBy(o => o).Distinct();
myCollection.Distinct().OrderBy(o => o);

1
不,我的意思是说结果必须完全相同才能考虑优化。优化某些东西却得到不同的结果是没有意义的。 - michael

1
值得注意的是,在考虑如何优化LINQ查询时,您应该小心谨慎。例如,如果您使用声明式版本的LINQ执行以下操作:
public class Record
{
    public string Name { get; set; }
    public double Score1 { get; set; }
    public double Score2 { get; set; }
}


var query = from record in Records
            order by ((record.Score1 + record.Score2) / 2) descending
            select new
                   {
                       Name = record.Name,
                       Average = ((record.Score1 + record.Score2) / 2)
                   };

如果出于任何原因,您决定通过首先将平均值存储到变量中来“优化”查询,那么您将无法获得预期的结果:

// The following two queries actually takes up more space and are slower
var query = from record in Records
            let average = ((record.Score1 + record.Score2) / 2)
            order by average descending
            select new
                   {
                       Name = record.Name,
                       Average = average
                   };

var query = from record in Records
            let average = ((record.Score1 + record.Score2) / 2)
            select new
                   {
                       Name = record.Name,
                       Average = average
                   }
            order by average descending;

我知道并不是很多人使用对象的声明性LINQ,但这是一些值得思考的好东西。


0

这取决于相关性。假设您有很少的Code=3项目,则下一个订单将在小集合上工作,以按日期获取订单。

而如果您有许多具有相同CreatedDate的项目,则下一个订单将在更大的集合上工作,以按日期获取订单。

因此,在两种情况下,性能都会有所不同。


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