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

.NET面试题系列目录

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

提高性能的小技巧

避免遍历整个序列

当咱们仅须要一个资料的时候,咱们能够考虑使用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查询是否已经有了现成的方法(例如得到最后一个元素)。
  • 撰写单元测试来保证你的优化的正确性。
相关文章
相关标签/搜索