我们在使用LINQ的代码段中遇到了轻微的性能问题,并且提出了一个关于LINQ如何在查找方面工作的问题
我的问题是这个(请注意我已经更改了所有代码,因此这是代码的指示性示例,而不是真实场景):
鉴于
public class Person {
int ID;
string Name;
DateTime Birthday;
int OrganisationID;
}
如果我有一个说100k Person对象的列表,然后是一个日期列表,比如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(用于查找具有organisationID 123的用户需要完成的循环)
乘以
1000(列表中的日期数量)
乘以
x(具有针对该日期检查的organisationID 123的用户数量)
这是很多迭代!
如果我将personBirthdays的代码更改为:
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到对象。
答案 0 :(得分:8)
这应该将10k作为倍数移除,并且只执行一次?
这意味着,不是迭代personList
100k次,而是为每次迭代执行where
和select
次操作迭代生成的List
100k次,并且where
和select
操作只会在基础数据源上执行一次。
问题是,这似乎太容易了,我确信LINQ在某处做了一些聪明的事情,这应该意味着这不会发生。
不,你的第一个查询只是你不应该使用LINQ做的事情,你应该把查询的结果放在一个数据结构中,如果你打算多次迭代它们(这是什么你改变了。)
您可以使用适当的数据结构进一步改进此查询。搜索List
效率相当低,因为它需要进行线性搜索。最好使用HashSet
来存储查询结果。 HashSet
在平均情况下具有O(1)搜索速度,而不是List
的O(n)搜索时间。
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()));
}
答案 1 :(得分:3)
这是典型的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());
答案 2 :(得分:0)
我假设您指的是LINQ-to-Objects,因为每个LINQ提供程序都有自己的实现(LINQ-to-SQL,LINQ-to-Entities,LINQ-to-XML,LINQ-to-anything) )。
以personBirthdays
为例,表达式的创建是为了迭代完整的结果集,因此LINQ不能自动将结果实现为数组或列表。
这些操作非常不同:
personBirthdays.Distinct()
personBirthdays.FirstOrDefault(b => b.Month == 7)
personBirthdays.Select(b => b.Year).Distinct()
作为一种技术的LINQ,“聪明”是允许构造表达式树并推迟执行。这可以防止 - 在上面的第三个例子中 - 100k迭代获得生日,然后另外100k选择年份,然后是最后的,昂贵的传递来组装不同的值。
LINQ消费者(你)必须拥有表达式的命运。如果您知道结果集将被多次迭代,那么您有责任将它们具体化为数组或列表。