【问题标题】:Does this LINQ code perform multiple lookups on the original data?此 LINQ 代码是否对原始数据执行多次查找?
【发布时间】:2012-12-11 15:44:25
【问题描述】:

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

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

给定

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

如果我有一个包含 10 万个 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(需要执行的循环以查找组织 ID 为 123 的用户)
乘以
1000(列表中的日期数量)
乘以
x(当日要检查组织ID 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-to-Objects。

【问题讨论】:

  • @HamletHakobyan 鉴于他提出的问题,没关系。
  • 是的。在第一个 sn-p 中,您有 dateList.Count personBirthdays 的迭代。
  • 嘿伙计,通过传递dateList 作为参数,搜索使用EF 生成IN 查询的解决方案,如果您的dateList 长度不是很长,这将固有地解决您的问题。跨度>
  • @Jani 我们不知道他是否使用 EF。他可能正在对对象进行 linq,或者使用其他提供程序。
  • @Servy ,如果 personListIQueryablepersonBirthdays 将被转换为适当的上下文查询,并且在 foreach 循环中它将是 dateList.Count 查询数据库而不是 100k 迭代IEnumerable&lt;Person&gt;.

标签: c# linq linq-to-objects


【解决方案1】:

这应该将 10k 作为倍数删除,并且只执行一次?

这意味着不是迭代 personList 100k 次,而是执行 whereselect 操作对于每个迭代,您将迭代生成的 List 100k 次,whereselect 操作将只对底层数据源执行一次。

问题是这似乎太容易了,我确信 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()));
}

【讨论】:

  • 难道你的 foreach 不会用更糟糕的方式来做吗?由于 Where 会强制进行迭代,那么需要进行另一次迭代以打印出日期?或者 dates.contains 会做一些花哨的事情吗?可能有intersect 代替吗?除此之外,很好的答案......正是我想要的。现在想办法告诉开发者...
  • 通过将早期查询放入HashSet,它会急切地评估该查询并将其解析为各个日期。然后,在foreach 中,它遍历dateList 中的每个日期并在该集合上执行ContainsContains,在 HashSet 上,是一个非常高效的操作。
【解决方案2】:

这是典型的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());

【讨论】:

  • HashSet 的构造函数采用IEnumerable&lt;T&gt;,不需要ToArray
  • 或者用.ToDictionary()代替.ToList()
  • @ChristopherStevenson 但他在逻辑上没有键/值关系;使用 HashSet 而不是 Dictionary 是合适的。很遗憾,在 LINQ 中没有包含 ToHashSet,但如果您愿意,您可以自己轻松地制作自己的方法。
  • 塔达:public static HashSet&lt;T&gt; ToHashSet&lt;T&gt;(this IEnumerable&lt;T&gt; src) { return new HashSet&lt;T&gt;(src); }
  • ToArray() 不仅没有必要,而且实际上是一个巨大的性能消耗者。
【解决方案3】:

我假设您指的是 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 使用者(您)必须拥有表达式的命运。如果您知道结果集将被迭代多次,那么您有责任将它们具体化为数组或列表。

【讨论】:

  • it is not a foregone conclusion that the expression was created for the purpose of iterating 嗯,实际上是这样。它必须在给定上下文的情况下实现IEnumerable,并定义枚举正是它的意思。 What LINQ as a technology does that is "clever" is to allow the construction of an expression tree and [...] 您将 Linq 声明为对象,但没有涉及表达式或表达式树,这仅在 IQueryable 方面。它通过保留对迭代器和委托的引用来延迟执行。
  • 非常感谢您的回答,但是,Servy 的迭代差异更清楚。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-23
  • 1970-01-01
  • 2021-04-19
  • 2014-10-25
  • 1970-01-01
相关资源
最近更新 更多