此LINQ代码是否对原始数据执行多次查找



我们在使用LINQ的一段代码中遇到了一个轻微的性能问题,这引发了一个关于LINQ在查找方面如何工作的问题

我的问题是(请注意,我已经更改了所有代码,所以这是代码的指示性示例,而不是真实场景):

给定

public class Person {
int ID;
string Name;
DateTime Birthday; 
int OrganisationID;
}

如果我有一个10万个人对象的列表,然后是一个日期列表,比如1000,我运行了以下代码:

var personBirthdays = from Person p in personList
where p.OrganisationID = 123
select p.Birthday;
foreach (DateTime d in dateList)
{
if (personBirthdays.Contains(d))
Console.WriteLine(string.Format("Date: {0} has a Birthday", d.ToShortDateString()));
}

生成的代码将是的迭代

100k(查找组织ID为123的用户所需的循环)
乘以
1000(列表中的日期数量)
乘以
x(要检查日期的组织ID为23的用户数量)

这是大量的迭代!

如果我把个人生日代码改成这样:

List<DateTime> personBirthdays = 
(from Person p in personList
where p.OrganisationID = 123
select p.Birthday).ToList();

这应该删除100k作为倍数,只做一次?

所以你会有100k+(1000*x)而不是(100k*1000*x)。

问题是,这似乎太容易了,我相信LINQ在某个地方做了一些聪明的事情,这应该意味着这不会发生。

如果没有人回答,我会进行一些测试并返回报告。

清晰更新:我们不考虑数据库查找,personList对象是内存中列表对象。这都是LINQ对象。

这应该删除10k作为倍数by,只做一次?

这意味着,不是迭代personList100k次,而是为每一次迭代执行whereselect操作,您将迭代得到的List100k次并且whereselect操作将只在底层数据源上执行一次。

问题是这似乎太容易了,我相信LINQ在某个地方做了一些聪明的事情,这应该意味着这不会发生。

不,您的第一个查询只是不应该使用LINQ进行的事情,如果您计划多次迭代(这就是您所更改的),则应该获取查询结果并将其放入数据结构中。

通过使用适当的数据结构,可以进一步改进此查询。在List上搜索是相当低效的,因为它需要进行线性搜索。最好使用HashSet来存储查询的结果。与List的O(n)搜索时间相比,HashSet在平均情况下具有O(1)搜索速度。

var dates = new HashSet<DateTime>(from Person p in personList
where p.OrganisationID = 123
select p.Birthday);
foreach (DateTime d in dateList.Where(date => dates.Contains(date)))
{
Console.WriteLine(string.Format("Date: {0} has a Birthday", d.ToShortDateString()));
}

这是典型的select n+1问题,在应用.ToList()后,您已经部分解决了它。下一步可能如下:您不断迭代personBirthdays列表,将其替换为HashSet,您可以更快地执行Contains(d)并删除重复:

var personBirthdays = new HashSet<DateTime>((from Person p in personList
where p.OrganisationID = 123
select p.Birthday).ToArray());

我假设您指的是LINQ to Objects,因为每个LINQ提供程序都有自己的实现(LINQ to SQL、LINQ to Entities、LINQ to XML、LINQ toanything)。

personBirthdays为例,创建表达式是为了遍历整个结果集,这并不是一个必然的结论,因此LINQ无法自动将结果具体化为数组或列表。

这些操作非常不同:

personBirthdays.Distinct()
personBirthdays.FirstOrDefault(b => b.Month == 7)
personBirthdays.Select(b => b.Year).Distinct()

LINQ作为一种"聪明"的技术,允许构建表达式树并推迟执行。这就是为什么——在上面的第三个例子中——10万次迭代来获得生日,然后再10万次选择年份,然后是最后一次昂贵的迭代来收集不同的值。

LINQ消费者(你)必须拥有表达的命运。如果您知道结果集将被迭代多次,那么您就有责任将它们具体化为数组或列表。

最新更新