.NET面试题系列[15] - LINQ:性能

.NET面试题系列目录

当你使用LINQ to SQL时,请使用工具(比如LINQPad)查看系统生成的SQL语句,这会帮你发现问题可能发生在何处。

提升性能的小技巧

避免遍历整个序列

当我们仅需要一个资料的时候,我们可以考虑使用First / FirstOrDefault / Take / Any等方法,它们都会在取得合乎要求的资料后退出,而不会遍历整个序列(除非最后一个资料才是合乎要求的哈哈)。而类似ToList / Max / Last / Sum / Contain等方法显而易见会遍历整个序列。

例如你判断一个集合是否有成员时,请使用Any而不是Count==0。因为如果该集合有极多成员时,Count遍历是非常消耗时间的。

避免重复枚举同一序列

如果你在重复枚举同一个序列,你可能会收到如下的警告:

一般看到这个提示,你需要一个ToList/ToDictionary/ToArray等类似的方法。重复枚举是不必要且浪费时间的。另外,如果程序涉及多线程,或者你的序列含有随机因素,你的每次枚举的结果可能不同。我们只需要枚举同一序列一次,之后将结果储存为一个泛型集合即可。

例如我们的序列带有随机数:

此时我们会遍历序列四次。但每次序列都会不同。例如如果我们呼叫Sum方法四次,则可能会出现4个不同的和。我们必须使用ToList方法强制LINQ提前执行。

避免毫无必要的缓存整个序列

在获得序列最后一个成员时,我们有很多方法:

其中前两个方法都不是最好的。当我们调用LINQ的某些方法时,我们缓存了整个序列,而这可能是不必要的。我们根本不需要将整个序列留在内存中,只需要获得最后一个成员就可以了。

何时使用ToList / ToArray / ToDictionary等方法

根据前面两点,我们可以总结出来何时使用ToList / ToArray / ToDictionary等方法:

  • 你确定你需要整个序列的时候
  • 你确定你会遍历整个序列多于一次的时候
  • 如果序列不是很大的时候(因为ToList / ToArray / ToDictionary等方法将会在堆上分配一个序列对象)

是否返回IEnumerable<T>?

是否返回IEnumerable<T>,或者返回一个List,或者数组?注意当你返回IEnumerable<T>时,你并没有开始遍历这个序列(只有当你强制LINQ执行时,才会执行这个返回IEnumerable<T>的方法)。当然如果数据来自远端,你还可以选择IQueryable<T>,它不会把资料一股脑拉下来,而是做完所有的筛选之后,才ToList,把资料从远端下载下来。所以在使用ORM时,如果它用到了IQueryable,请将你的查询也写成表达式而不是委托的形式。参考:http://www.cnblogs.com/SieAppler/p/3501475.html

另外,我们可以通过返回IEnumerable<T>而不是List或数组,来给予呼叫者最大的便利。(给他一个最General类型的返回)

SELECT N+1问题

假设你有一个父表(例如:汽车),其关联一个子表,例如轮子(一对多)。现在你想对于所有的父表汽车,遍历所有汽车,然后打印出来所有轮子的信息。默认的做法将是:

SELECT CarId FROM Cars;

然后对于每个汽车:

SELECT * FROM Wheel WHERE CarId = ?

这会SELECT 2个表一共N(子表的行数)+1(父表)次,故称为SELECT N+1问题。

考察下面的代码。假设album是一个表,artist是另外一个表,album和artist是一对多的关系:

我们知道foreach会强制LINQ执行,于是,我们可以想象这也是一个SELECT N+1问题的例子:先获得所有album(SELECT * FROM ALBUM),然后遍历,对每一个album的Title,检查其是否包含关键字,如果符合,再去SELECT 表artist,共SELECT N+1次。我们可以通过LINQPAD或其他方式检查编译器生成的SELECT语句数目,一定会是N+1条SQL语句。

解决方法:使用一个匿名对象作为中间表格,预先将两个表join到一起

生成的SQL将只有一句话!

这篇文章中的第三点,就是一个典型的SELECT N+1问题。在代码中,选择了前100个score(一条SQL),然后对所有score进行遍历,从表Student中获得Name的值(100条SQL)。

解决方法也在文章中给出了,就是将两个表连到一起。该文章的“联表查询统计”这一节,说的还是这个问题。简单说,还是每次都用LINQPad工具,看看最终生成的SQL到底长啥样。(当然还有很多其他工具,或者最基本的就是用SQL Profiler不过比较麻烦)

LINQ to SQL的性能问题

提升从数据库中拿数据的速度,可以参考以下几种方法:

  1. 在数据库中的表中定义合适的索引和键
  2. 只获得你需要的列(使用ViewModel或者改进你的查询)和行(使用IQueryable<T>)
  3. 尽可能使用一条查询而不是多条
  4. 只为了展示数据,而不进行后续修改时,可以使用AsNoTracking。它不会影响生成的SQL,但它可以令系统少维护很多数据,从而提高性能
  5. 使用Reshaper等工具,它可能会在你写出较差的代码时给出提醒

我们可以通过很多工具来获得系统产生的SQL语句,例如LINQPAD或者SQL Profiler。在EF6中,我们还可以使用这样的方法:

注意:编译器不一定能够将你的LINQ语句翻译为SQL,例如字符串的IndexOf方法就不被支持。

使用LinqOptimizer提升LINQ语句的性能

LinqOptimizer可以通过nuget获得。你可以通过在IEnumerable<T>上调用AsQueryExpr方法来令LinqOptimizer优化你的LINQ语句。使用Run方法执行:

LINQ:替代选择

在没有找到性能瓶颈之前,不要过早优化。

  1. 是否存在需要长时间运行的LINQ语句?
  2. 是否在数据库上取得数据,并运行LINQ语句?(这意味着存在一个LINQ语句到SQL的表达式转换)
  3. 数据规模是否巨大?
  4. 是否需要重复极其多次运行相同的LINQ语句?

LINQ VS Foreach(重复极其多次运行相同的LINQ语句)

在什么情况下,LINQ反而不如Foreach表现好?两者的性能差距是怎样的?下面的例子的序列有一千万个成员,我们对它们做些简单运算。

结果:

可以看到Foreach的表现稍好一点。LINQ的额外开销在于lambda表达式转换为委托的形式,而foreach不需要。虽然这一点点额外开销对于普通的情况基本可以忽略,但如果重复一千万次,则性能可能会有较为明显的差异。

LINQ VS PLINQ(重复运行相同的LINQ语句)

显而易见,如果我们重复运行相同的任务,且任务之间又没有什么关系(不需要对结果进行汇总),此时我们可以想到用多线程来解决问题,重复利用系统的资源:

执行后只用了423毫秒。通常来说,执行的结果将等于Foreach的时间,除以系统CPU的核数量。当CPU为双核时,速度大概可以提升一倍。当然,对于单核机器来说,PLINQ是没有意义的。

当你的机器拥有多核,并且你处理相同的任务时(例如从不同的网站下载内容,并做相同的处理),可以考虑使用PLINQ。不过PLINQ也需要一些额外开销:它访问线程池,新建线程,将任务分配到各个线程中,然后还要收集任务的结果。所以,你需要测量PLINQ是否真的可以加快你的代码的运行速度。

自定义ORM

通常,只有在如下情况下才会考虑将自己写的ORM投入生产使用:

  • 存在一些特定的复杂查询,在项目中广泛出现,此时自己写的ORM做了很多优化,表现好于EF
  • 存在一些特定的业务逻辑,例如将表达式解析为XML等,EF没有对应的功能
  • 你的项目对性能要求达到了非常苛刻的程度,导致EF的一些性能可以接受的方法在你这里变成了不能接受。例如EF使用了反射,但如果你的ORM只用于你开发的软件,所有的情况你都可以事先预计,那你也可以不用反射

而大部分ORM开发出来的目标仅仅是:

  • 令查询语法更加接近SQL
  • 加入了若干语法糖或代码生成快捷方式,令编写代码速度稍微加快
  • 性能和EF相差无几,有些甚至还不如EF
  • 没有经过彻底的测试
  • 自学使用

通常,自己开发一套ORM需要很长的时间,才能保证没有错误,并用于生产环境。大部分情况下,EF已经是一个不错的选择。性能是双刃剑,它可能也会毁了你的代码,让你的代码难以维护。

LINQ性能问题:总结

  • 使用LINQPad等工具观察生成的SQL。当你优化之后,再次在LINQPad上运行看看是否造成了可观的性能提升。
  • 是否需要在数据库上筛选数据,并运行LINQ语句?如果是的话,考虑返回IQueryable<T>,并考察编译器构建的中间SQL语句。
  • 数据规模是否巨大?避免过早的ToList,返回IEnumerable/ IQueryable<T>类型的巨大规模的数据。
  • 是否需要重复极其多次运行相同的LINQ语句?考虑使用foreach或者PLINQ来优化性能。
  • 使用LinqOptimizer来优化LINQ语句。
  • 使用Reshaper等工具,它可能会在你写出较差的代码时给出提醒。
  • 上MSDN,nuget查询是否已经有了现成的方法(例如获得最后一个元素)。
  • 撰写单元测试来保证你的优化的正确性。
时间: 2024-10-09 16:28:49

.NET面试题系列[15] - LINQ:性能的相关文章

.NET面试题系列[13] - LINQ to Object

.NET面试题系列目录 名言警句 "C# 3.0所有特性的提出都是更好地为LINQ服务的" - Learning Hard LINQ是Language Integrated Query(语言集成查询)的缩写,读音和单词link相同.不要读成“lin-Q”. LINQ to Object将查询语句转换为委托.LINQ to Entity将查询语句转换为表达式树,然后再转换为SQL. LINQ的好处:强类型,相比SQL语句它更面向对象,对于所有的数据库给出了统一的操作方式. LINQ的一些

算法系列15天速成——第三天 七大经典排序【下】

原文:算法系列15天速成--第三天 七大经典排序[下] 今天跟大家聊聊最后三种排序: 直接插入排序,希尔排序和归并排序. 直接插入排序: 这种排序其实蛮好理解的,很现实的例子就是俺们斗地主,当我们抓到一手乱牌时,我们就要按照大小梳理扑克,30秒后, 扑克梳理完毕,4条3,5条s,哇塞......  回忆一下,俺们当时是怎么梳理的. 最左一张牌是3,第二张牌是5,第三张牌又是3,赶紧插到第一张牌后面去,第四张牌又是3,大喜,赶紧插到第二张后面去, 第五张牌又是3,狂喜,哈哈,一门炮就这样产生了.

iOS面试题系列之Objective-C相关

1.简述你项目中常用的设计模式.它们有什么优缺点? 常用的设计模式有:代理.观察者.单例. (1)单例:它是用来限制一个类只能创建一个对象.这个对象中的属性可以存储全局共享的数据.所有的类都能访问.设置此单例中的属性数据. 优点:是它只会创建一个对象容易供外界访问,节约性能. 缺点:是一个类只有一个对象,可能造成责任过重,在一定程度上违背了"单一职责原则".单例模式中没有抽象层,所以单例类的扩展有很大的困难.不能过多创建单例,因为单例从创建到程序关闭前会一直存在,过多的单例会影响性能,

.NET面试题系列[10] - IEnumerable的派生类

.NET面试题系列目录 IEnumerable分为两个版本:泛型的和非泛型的.IEnumerable只有一个方法GetEnumerator.如果你只需要数据而不打算修改它,不打算为集合插入或删除任何成员(例如从远端拿回数据显示),则你不需要任何比IEnumerable更复杂的接口. ICollection继承IEnumerable.可以使用Count方法统计集合的大小.(注意非泛型版本的ICollection并没有Add,Remove等方法)但在实际情况中,我们通常使用ICollection的继

.NET面试题系列[11] - IEnumerable&lt;T&gt;的派生类

“你每次都选择合适的数据结构了吗?” - Jeffery Zhao .NET面试题系列目录 ICollection<T>继承IEnumerable<T>.在其基础上,增加了Add,Remove等方法,可以修改集合的内容.IEnumerable<T>的直接继承者还有Stack<T>和Queue<T>. 所有标准的泛型集合都实现了ICollection<T>.主要的几个继承类有IList<T>,IDictionary<K

.net必问的面试题系列之面向对象

上个月离职了,这几天整理了一些常见的面试题,整理成一个系列给大家分享一下,机会是给有准备的人,面试造火箭,工作拧螺丝,不慌,共勉. 1.net必问的面试题系列之基本概念和语法 2.net必问的面试题系列之面向对象 3.net必问的面试题系列之设计模式 4.net必问的面试题系列之集合.异常.泛型 5.net必问的面试题系列之简单算法 6.net必问的面试题系列之数据库 7.net必问的面试题系列之web前端 问题目录 1.通常采用的访问修饰符有哪五种 2.多态的理解 3.什么是构造函数 4.重写

C#刷遍Leetcode面试题系列连载(1) - 入门与工具简介

目录 为什么要刷LeetCode 刷LeetCode有哪些好处? LeetCode vs 传统的 OJ LeetCode刷题时的心态建设 C#如何刷遍LeetCode 选项1: VS本地Debug + 在线验证后提交 选项2: VS Code本地Debug + 在 LeetCode 插件中验证和提交 为什么要刷LeetCode 大家都知道,很多对算法要求高一点的软件公司,比如美国的FLAGM (Facebook.LinkedIn.Amazon/Apple.Google.Microsoft),或国

【转载】.NET面试题系列[0] - 写在前面

原文:.NET面试题系列[0] - 写在前面 索引: .NET框架基础知识[1] - .NET框架基础知识(1) http://www.cnblogs.com/haoyifei/p/5643689.html .NET框架基础知识[2] - .NET框架基础知识(2) http://www.cnblogs.com/haoyifei/p/5646288.html .NET框架基础知识[3] - C# 基础知识(1) - http://www.cnblogs.com/haoyifei/p/565054

[转] 擎天哥as3教程系列第二回——性能优化

所谓性能优化主要是让游戏loading和运行的时候不卡. 一  优化fla导出的swf的体积? 1,  在flash中,舞台上的元件最多,生成的swf越大,库里面有连接名的元件越多,swf越大.当舞台上没有元件且库里面的元件没有连接名的话生成的swf最小. 2,  一个flash动画有10帧,10帧上面全部是位图和用一个位图播放器播放这10张图片谁消耗的cpu更高? 答:flash动画播放消耗性能更高,因为swf文件里虽然也是位图,但是swf里面的播放机制是能播放位图,矢量图,声音,视频等.所以