.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查询是否已经有了现成的方法(例如获得最后一个元素)。
  • 撰写单元测试来保证你的优化的正确性。

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

  1. .NET面试题系列[14] - LINQ to SQL与IQueryable

    .NET面试题系列目录 名言警句 "理解IQueryable的最简单方式就是,把它看作一个查询,在执行的时候,将会生成结果序列." - Jon Skeet LINQ to Obje ...

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

    .NET面试题系列目录 名言警句 "C# 3.0所有特性的提出都是更好地为LINQ服务的" - Learning Hard LINQ是Language Integrated Que ...

  3. .NET面试题系列[0] - 写在前面

    .NET面试题系列目录 .NET面试题系列[1] - .NET框架基础知识(1) .NET面试题系列[2] - .NET框架基础知识(2) .NET面试题系列[3] - C# 基础知识(1) .NET ...

  4. .NET面试题系列[12] - C# 3.0 LINQ的准备工作

    "为了使LINQ能够正常工作,代码必须简化到它要求的程度." - Jon Skeet 为了提高园子中诸位兄弟的英语水平,我将重要的术语后面配备了对应的英文. .NET面试题系列目录 ...

  5. .NET面试题系列[8] - 泛型

    “可变性是以一种类型安全的方式,将一个对象作为另一个对象来使用.“ - Jon Skeet .NET面试题系列目录 .NET面试题系列[1] - .NET框架基础知识(1) .NET面试题系列[2] ...

  6. .NET面试题系列[11] - IEnumerable<T>的派生类

    “你每次都选择合适的数据结构了吗?” - Jeffery Zhao .NET面试题系列目录 ICollection<T>继承IEnumerable<T>.在其基础上,增加了Ad ...

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

    .NET面试题系列目录 IEnumerable分为两个版本:泛型的和非泛型的.IEnumerable只有一个方法GetEnumerator.如果你只需要数据而不打算修改它,不打算为集合插入或删除任何成 ...

  8. net必问的面试题系列之基本概念和语法

    上个月离职了,这几天整理了一些常见的面试题,整理成一个系列给大家分享一下,机会是给有准备的人,面试造火箭,工作拧螺丝,不慌,共勉. 1.net必问的面试题系列之基本概念和语法 2.net必问的面试题系 ...

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

    上个月离职了,这几天整理了一些常见的面试题,整理成一个系列给大家分享一下,机会是给有准备的人,面试造火箭,工作拧螺丝,不慌,共勉. 1.net必问的面试题系列之基本概念和语法 2.net必问的面试题系 ...

随机推荐

  1. Trick蠕虫病毒来袭!幕后主使竟是一名高中生“黑客”!

    黑客一直是美国电影中的重要元素,很多经典大片中都有黑客的身影,如战争游戏.黑客帝国等.电影中黑客总是神通广大.行侠仗义,<战争游戏>中的年轻黑客大卫•莱特曼利用黑客技术避免引爆核武器,&l ...

  2. Problem with "AnyConnect was not able to establish connection to the specified secure gateway."

    Cisco的VPN客户端最近报"AnyConnect was not able to establish connection to the specified secure gateway ...

  3. oracle is not in the sudoers file. This incident will be reported.

    准备把OS的root禁用了,所以其他用户要执行使用root执行的操作时,需要使用sudo. 在没有配置sudo的时候,执行sudo会出现类似以下的报错: [oracle@test ~]$ sudo / ...

  4. 进击的Python【第七章】:Python的高级应用(四)面向对象编程进阶

    Python的高级应用(三)面向对象编程进阶 本章学习要点: 面向对象高级语法部分 静态方法.类方法.属性方法 类的特殊方法 反射 异常处理 Socket开发基础 一.面向对象高级语法部分 静态方法 ...

  5. 【转】Wireshark基本用法

    原地址:http://blog.jobbole.com/70907/ 按照国际惯例,从最基本的说起. 抓取报文: 下载和安装好Wireshark之后,启动Wireshark并且在接口列表中选择接口名, ...

  6. C# 解析JSON的几种办法

    欲成为海洋大师,必知晓海中每一滴水的真名. 刚开始只是想找一个转换JSON数组的方法,结果在MSDN翻到一大把. 搜索过程中免不了碰到一大堆名词:WCF => DataContract => ...

  7. Angular动画(ng-repeat)

    ng-repeat 动画 根据列表元素的插入与移除,触发相应的代码添加动画 <!doctype html> <html lang="en" ng-app=&quo ...

  8. NOIp 2016 总结

    NOIp 2016 总结 -----YJSheep Day 0 对于考前的前一天,晚自习在复习图论的最短路和生成树,加深了图的理解.睡得比较早,养足精力明日再战. Day 1 拿到题目,先过一边,题目 ...

  9. jQuery.zTree的跳坑记录

    最近项目用到树型结构的交互,一开始并不打算选择zTree,为了项目进度我妥协了,这一妥协后果就是我进坑了,在2天的挣扎中,我终于跳出坑了,活了下来,有一些感慨纪录下来. 有一个业务场景需要2个树型结构 ...

  10. [DataMining]WEEK1 - text-retrieval and search engine

    What does a computer have to do in order to understand a natural language sentence? What is ambiguit ...