我最近已经开始使用LINQ了很多,而且我还没有真正提到任何LINQ方法的运行时复杂性。显然,这里有许多因素在起作用,因此让我们将讨论范围限制在普通的IEnumerableLINQ to Objects提供程序上。此外,我们假设任何Func以选择器/增幅器/等形式传入的都是廉价的O(1)操作。
IEnumerable
Func
它似乎很明显,所有的单次操作(Select,Where,Count,Take/Skip,Any/All,等)将是O(n)的,因为他们只需要步行的顺序一次; 尽管即使这样也会受到懒惰的影响。
Select
Where
Count
Take/Skip
Any/All
对于更复杂的操作,事情变得更加模糊。集合类运算符(Union,Distinct,Except等)使用工作GetHashCode在默认情况下(据我所知),所以它似乎是合理的假设他们使用一个哈希表内,使这些操作为O(n)为好,一般。那使用的版本IEqualityComparer呢?
Union
Distinct
Except
GetHashCode
IEqualityComparer
OrderBy需要排序,因此最有可能我们正在查看O(n log n)。如果已经排序怎么办?如果我说OrderBy().ThenBy()并为两者提供相同的密钥怎么办?
OrderBy
OrderBy().ThenBy()
我可以使用排序或哈希查看GroupBy(和Join)。哪有
GroupBy
Join
Contains在a上将为O(n)List,但在a上将为O(1)HashSet-LINQ是否检查基础容器以查看其是否可以加快速度?
Contains
List
HashSet
真正的问题- 到目前为止,我一直坚信作业是高效的。但是,我可以依靠吗?例如,STL容器清楚地指定了每个操作的复杂性。.NET库规范中是否对LINQ性能有任何类似的保证?
更多问题(针对评论):并 没有真正考虑开销,但是我没想到简单的Linq-to-Objects会有很多。CodingHorror帖子谈论的是Linq-to- SQL,在这里我可以理解解析查询并使SQL增加成本-对象提供程序也有类似的成本吗?如果是这样,使用声明性或函数语法有什么不同?
有非常少的保证,但是有一些优化:
使用索引访问扩展方法,比如ElementAt,Skip,Last或者LastOrDefault,将检查底层式工具与否IList<T>,让你得到O(N)的O(1)访问来代替。
ElementAt
Skip
Last
LastOrDefault
IList<T>
该Count方法检查ICollection实现,以便此操作为O(1)而不是O(N)。
ICollection
Distinct,GroupBy Join,我相信也设置汇总的方法(Union,Intersect和Except)使用散列,所以他们应该是接近O(N),而不是O(N²)。
Intersect
Contains检查ICollection实现,因此如果基础集合也是O(1),例如a ,则 可能 为O(1)HashSet<T>,但这取决于实际的数据结构,因此不能保证。哈希集会覆盖该Contains方法,这就是为什么它们是O(1)的原因。
HashSet<T>
OrderBy 方法使用稳定的快速排序,因此它们是O(N log N)个平均情况。
我认为这涵盖了大多数(如果不是全部)内置扩展方法。确实很少有性能保证。Linq本身将尝试利用高效的数据结构,但是编写潜在的低效率代码不是免费的通行证。