LINQ 性能常见问题解答
Posted
技术标签:
【中文标题】LINQ 性能常见问题解答【英文标题】:LINQ performance FAQ 【发布时间】:2010-10-28 15:27:33 【问题描述】:我正在尝试掌握 LINQ。最困扰我的是,即使我对语法理解得更好,我也不想为了表现力而不知不觉地牺牲性能。
对于“有效的 LINQ”,它们是否是任何好的集中信息库或书籍?如果做不到这一点,您个人最喜欢的高性能 LINQ 技术是什么?
我主要关注 LINQ to Objects,但当然也欢迎所有关于 LINQ to SQL 和 LINQ to XML 的建议。谢谢。
【问题讨论】:
根据我的个人经验,LINQ 的速度与您自己编写的代码的速度完全相同。在许多情况下,由于延迟评估(以及 .NET 团队已经为您完成的疯狂优化),它甚至更快。 我不知道您在什么环境中工作,但我从事对性能至关重要的企业应用程序。每种方法都会定期进行性能审查,并且 LINQ 被严格禁止。使用 LINQ 会受到惩罚,因为必须连续一周每天按需为办公室里的每个人泡茶/咖啡。 @steve 我们在任何情况下都不使用 LINQ。老派的“for”和“foreach”循环是常态 @Rex M - 这次交流正是我问这个问题的原因。我发现很难找到 LINQ 比手动编码循环有所改进的案例,但是当 LINQ 彻底失败时,有很多反例。如果我们无知,请教育我们。 @Rex M - 也许是这样。我只是想知道我在这里看到的性感 LINQ 答案中有多少是最佳的。表现力很诱人。感谢您的想法。 【参考方案1】:Linq 作为一种内置技术,具有性能优势和劣势。 .NET 团队对扩展方法背后的代码给予了相当多的性能关注,它提供惰性评估的能力意味着对一组对象执行大多数操作的成本分散在需要操作集的更大算法中.但是,您需要了解一些事情,这些事情可能会影响您的代码性能。
首先,Linq 不会神奇地为您的程序节省执行操作所需的时间或内存。它只是可能会延迟这些操作,直到绝对需要。 OrderBy() 执行 QuickSort,这将花费 nlogn 时间,就像您编写自己的 QuickSorter 或在正确的时间使用 List.Sort() 一样。因此,在编写查询时,请始终注意您要求 Linq 对系列执行的操作;如果不需要进行操作,请重新构建查询或方法链以避免它。
出于同样的原因,某些操作(排序、分组、聚合)需要了解它们所作用的整个集合。系列中的最后一个元素可能是操作必须从其迭代器返回的第一个元素。最重要的是,因为 Linq 操作不应该改变它们的源可枚举,但他们使用的许多算法会(即就地排序),这些操作最终不仅会评估,而且会将整个可枚举复制到具体的有限结构中,执行操作,并通过它让步。因此,当您在语句中使用 OrderBy() 并从最终结果中请求一个元素时,给它的 IEnumerable 可以产生的所有内容都会被评估,作为数组存储在内存中,排序,然后返回一个元素时间。道德是,任何需要有限集而不是可枚举的操作都应尽可能放在查询的最后,允许其他操作,如 Where() 和 Select() 以减少源集的基数和内存占用。
最后,Linq 方法极大地增加了系统的调用堆栈大小和内存占用。每个必须知道整个集合的操作都将整个源集合保存在内存中,直到最后一个元素被迭代,并且每个元素的评估将涉及一个调用堆栈,其深度至少是链或子句中方法数量的两倍在您的内联语句中(调用每个迭代器的 MoveNext() 或产生 GetEnumerator,以及沿途对每个 lambda 的至少一个调用)。与执行相同操作的智能设计的内联算法相比,这只会导致更大、更慢的算法。 Linq 的主要优点是代码简单。创建然后排序组值列表的字典不是很容易理解的代码(相信我)。微优化可以进一步混淆它。如果性能是您最关心的问题,那么不要使用 Linq;它将增加大约 10% 的时间开销和几倍于自己就地操作列表的内存开销。然而,可维护性通常是开发人员最关心的问题,而 Linq 肯定会在这方面提供帮助。
关于性能提升:如果您的算法性能是神圣的、不可妥协的首要任务,那么您将使用 C++ 等非托管语言进行编程; .NET 将慢得多,因为它是托管运行时环境,具有 JIT 本机编译、托管内存和额外的系统线程。我会采用“足够好”的哲学; Linq 本质上可能会导致减速,但如果您无法区分,而您的客户也无法区分,那么实际上就没有区别。 “过早优化是万恶之源”;让它发挥作用,然后寻找机会让它更高效,直到你和你的客户同意它足够好。它可能总是“更好”,但除非你想手工打包机器代码,否则你会发现没有一点可以宣布胜利并继续前进。
【讨论】:
“如果性能是您最关心的问题,那么就不要使用 Linq” - Web 开发怎么样?那里的性能可能是一个主要问题,因为您可以预期成千上万的用户同时执行操作,那么您会尝试在 Web 开发中避免使用 Linq 吗? Wellll... 是的,性能是一个问题,但多用户方面的大部分内容是由 ASP.NET 运行时和生命周期的体系结构为您处理的。此外,运行时性能问题通常与构成回发延迟大部分的网络带宽/延迟问题相形见绌。您仍然应该尽可能快地完成服务器端工作,但如果我的网站在实际流量负载下响应不够快,那么结构相对良好的 Linq 查询不会是我的第一个怀疑对象。【参考方案2】:只需了解 LINQ 在内部执行的操作即可获得足够的信息,以了解您是否受到性能影响。
这是一个简单的例子,其中 LINQ 有助于提高性能。考虑一下这种典型的老式方法:
List<Foo> foos = GetSomeFoos();
List<Foo> filteredFoos = new List<Foo>();
foreach(Foo foo in foos)
if(foo.SomeProperty == "somevalue")
filteredFoos.Add(foo);
myRepeater.DataSource = filteredFoos;
myRepeater.DataBind();
所以上面的代码将迭代两次并分配第二个容器来保存过滤后的值。多么浪费!比较:
var foos = GetSomeFoos();
var filteredFoos = foos.Where(foo => foo.SomeProperty == "somevalue");
myRepeater.DataSource = filteredFoos;
myRepeater.DataBind();
这只迭代一次(当转发器被绑定时);它只使用原始容器; filteredFoos
只是一个中间枚举数。而且,如果出于某种原因,您决定稍后不绑定中继器,则不会浪费任何东西。你甚至不迭代或评估一次。
当您进行非常复杂的序列操作时,您可以潜在地通过利用 LINQ 对链接和惰性求值的固有使用获得很多好处。同样,与任何事情一样,这只是了解它实际在做什么的问题。
【讨论】:
谢谢,这正是我希望看到的一个例子。我只是希望通过示例比试错法或先验分析更容易学习。 +1 表示两次迭代的样本。需要注意的是,过滤后的列表也会消耗内存,这会给 GC 带来一些压力。 我不明白第一个示例如何迭代两次。当您在其上调用 'DataBind()' 时,这是任何 'myRepeater' 的影响吗? @A.R.想想Repeater必须如何在内部工作——它有一个项目模板和一个数据源——它需要遍历源并将每个元素绑定到项目模板的副本。 我不明白为什么 LINQ 实现应该更快。至少在真正枚举中继器的正常情况下不会。检查 LINQ 源代码,您会看到...使用 LINQ,您将节省一次分配,但会执行更多代码(更多方法、更多检查),并且此代码的执行效率会降低(与调用者代码混合)。 【参考方案3】:影响性能的因素有很多。
通常,使用 LINQ 开发解决方案将提供相当合理的性能,因为系统可以构建表达式树来表示查询,而无需在构建时实际运行查询。只有当您迭代结果时,它才会使用此表达式树来生成和运行查询。
就绝对效率而言,针对预定义的存储过程运行您可能会看到一些性能损失,但通常采取的方法是使用提供合理性能的系统(例如 LINQ)开发解决方案,而不必担心百分之几的性能损失。如果查询运行缓慢,那么您可能会考虑优化。
现实情况是,大多数查询通过 LINQ 完成不会有丝毫问题。另一个事实是,如果您的查询运行缓慢,则更有可能是索引、结构等问题,而不是查询本身,因此即使在寻求优化时,您通常也不会触及 LINQ,只是它所针对的数据库结构。
对于处理 XML,如果您将文档加载并解析到内存中(例如基于 DOM 模型的任何内容,或 XmlDocument 或其他任何内容),那么您将获得比执行类似操作的系统更多的内存使用量引发事件以指示查找开始或结束标记,但不构建文档的完整内存版本(如 SAX 或 XmlReader)。缺点是基于事件的处理通常相当复杂。同样,对于大多数文档不会有问题 - 大多数系统都有几 GB 的 RAM,因此占用几 MB 来表示单个 XML 文档不是问题(而且您经常处理大量的 XML 文档,至少在某种程度上依次)。只有当您有一个占用 100 MB 的巨大 XML 文件时,您才会担心具体的选择。
请记住,LINQ 还允许您迭代内存中的列表等,因此在某些情况下(例如,您将在函数中一次又一次地使用一组结果),您可以使用 .ToList 或 .ToArray 返回结果。有时这可能很有用,尽管通常您希望尝试使用数据库的查询而不是内存中的查询。
至于个人最爱 - NHibernate LINQ - 它是一个对象关系映射工具,允许您定义类、定义映射细节,然后让它从您的类而不是反过来生成数据库,以及 LINQ支持非常好(肯定比 SubSonic 之类的要好)。
【讨论】:
【参考方案4】:在 linq to SQL 中,您不需要太在意性能。您可以以您认为最易读的方式链接您的所有陈述。 Linq 最后只是将所有语句转换为 1 个 SQL 语句,最后才被调用/执行(比如当你调用 .ToList()
如果您想在不同的条件下应用各种额外的语句,var
可以包含此语句而不执行它。只有当您想要将语句转换为对象或对象列表之类的结果时,才会最终执行。
【讨论】:
这仅适用于 LINQ to SQL。 他做到了。见第一次修订的最后一句话。 -1。有时 LinqToSql 会将您的查询转换为 n+1 语句。有时产生的查询不是最优的。 LinqToSql 查询需要数据库性能分析,这与来自任何其他来源的任何其他查询没有什么不同。【参考方案5】:有一个名为 i4o 的 codeplex 项目,我不久前使用过它,它可以帮助提高 Linq to Objects 的性能,例如在进行相等比较的情况下,例如
from p in People
where p.Age == 21
select p;
http://i4o.codeplex.com/ 我没有用 .Net 4 测试过它,所以不能肯定地说它仍然可以工作,但值得一试。 为了让它发挥它的魔力,你通常只需要用一些属性来装饰你的类,以指定应该索引哪个属性。当我之前使用它时,它只适用于相等比较。
【讨论】:
我发现,一般来说,如果我使用 LINQ 做一些需要索引来加速它的事情,通常最好自己做索引,因为 a) 我可能希望它们用于其他目的,并且 b ) 我控制索引的管理方式。这也让我可以创建更复杂的索引以上是关于LINQ 性能常见问题解答的主要内容,如果未能解决你的问题,请参考以下文章