Hello World
Spiga

.NET程序性能的基本要领

2014-05-14 22:18 by 老赵, 16977 visits

说起Roslyn大家肯定都已经有所耳闻了,这是下一代C#和VB.NET的编译器实现。Roslyn使用纯托管代码开发,但性能超过之前使用C++编写的原生实现。Bill Chiles是Roslyn的PM(程序经理,Program Manager),他最近写了一篇文章叫做《Essential Performance Facts and .NET Framework Tips》,其中总结了几条经验,目前是个CodePlex上的PDF文件,以后可能会发布在MSDN上。

他在文章里谈到以下几点:

  1. 不要进行过早优化。程序员有了一定经验以后,往往会对性能有所直觉,但也要避免盲目优化。
  2. 没有评测,便是猜测。例如,有的时候重复计算都比使用哈希表进行缓存来的快。
  3. 好工具很重要。这里他推荐了PerfView,这是个微软发布的免费工具,将来分析某些案例时我可能也会用到这个工具。
  4. 性能的关键,在于内存分配。凭直觉可能很多人会觉得编译器是一个CPU密集型的场景,但实际上它终究还是个IO密集型的程序。
  5. 其他一些细节。例如,对于字典的内存开销要有一些概念,还有例如我每次面试都会问到的class与struct的区别等等。

第4点值得多说几句。对于托管环境来说,GC对于性能的影响重大。假如一段程序写的不够GC友好,让GC发生的多,尤其是那种Stop-the-World GC,这对性能的影响远胜某些“多花了几条拷贝指令”之类的“探索”。而且很多时候,用户眼中的“性能”在于程序的“响应程度(responsiveness)”,一旦GC暂停了所有的线程,程序便很容易发生卡顿,这甚至不是通过简单评测程序性能能够体现出来的。

相较于Java平台来说,.NET已经是个相对GC友好的运行环境了。其中最重要的方面之一便是自定义值类型,即struct。struct让程序员进行一定程度上可控的内存分配,避免在堆上产生对象。而在Java中,只有几种原生类型是值类型,它们还不能包含成员。要知道在Java里无法使用一个未装箱的int值作为一个字典的键,这对一个.NET程序员来说可能很难想象,但事实便是如此。

当然,Java似乎已经有打算作这方面的改进,但离真正可用还遥遥无期。目前Java只能通过一些如逃逸分析的手段,发现某个对象不会被共享到堆上,于是便将其分配在栈上,避免对GC产生压力。

不过.NET提供再多对GC友好的功能,也抵不过开发人员的误用。Bill的文章里举了一些常见案例,这些其实都是每个.NET开发人员必须了解的基础。最后那个例子颇为有趣,他谈到,对于性能敏感的地方,有时候都要避免LINQ或Lambda。因为使用Lambda构造匿名函数时,编译器会产生闭包,因为所谓闭包,便是一个用来保存上下文的,分配在堆上的对象。此外,如List<T>的迭代器被有意实现为struct,但使用通用的LINQ接口,则会被转化为IEnumerable<T>IEnumerator<T>,进而产生装箱。

无独有偶,不久前@连城404在新浪微博上说到:

按照Michael的建议把HiveTableScan关键路径上的FP风格的代码换成while循环加可复用的mutable对象,扫表性能提升40%。”,这其实也正和这次的话题密切相关。

半夜不清醒,四则运算都算错了…发上条微博的时候实际上是提升了100%多点。之后继续蚊子腿上刮肉,不光是while循环,关键路径上的模式匹配代码也能刮出油水(例如省掉Array.unapplySeq调用开销)。目前使用LazySimpleSerDe的普通CVS表扫表性能提到2.2x,RCFile加上列剪枝可以提到3x。#Spark SQL#

Alan Perlis同样说过:

Lisp programmers know the value of everything but the cost of nothing.

可谓颇为有趣。常用的FP手段的确会带来性能开销,这是事实,不过假如你现在立即得出“不要用FP”或“还好没学FP”这样的结论,那我也只能用怜悯的眼光看着你了。

最后,您有减少内存分配,优化GC这方面的实践吗?不妨联系我吧,有机会我也会谈一下我在这方面的一些技巧和案例的。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

33 条回复

  1. 昂
    124.127.131.*
    链接

    2014-05-15 10:05:09

    很是期待下文啊,实际开发中对于拆箱装箱还比较注意,但是闭包这类就很随意了,即使有些方法提供了参数可以不用闭包也因为要强转一次而作罢。

    而实际开发中往往一个数据库索引,缓存带来的收益比去研究GC带来的收益大的多,所以上至领导下到小兵谁还会去关注GC呢

    就我的实际经验来讲,我"觉得"序列化(WCF、Memcache)对GC的压力是最大的

  2. 咖啡色
    205.156.160.*
    链接

    咖啡色 2014-05-15 17:26:56

    传说中的沙发?

  3. 咖啡色
    205.156.160.*
    链接

    咖啡色 2014-05-15 17:27:30

    老赵太坏了,不回复就看不见前面的回复吗?

  4. nihao
    219.127.93.*
    链接

    nihao 2014-05-15 17:31:34

    有A,B两个不同的C#程序,A产生了占用大量内存的DataTable对象后,1分钟后这个DataTable对象就可以设置为null,但是CLR并不会立即对这个对象进行GC。那么在未GC的情况下,如果这时B程序也需要使用大量内存,那么CLR会回收A进程DataTable占用的内存吗?

    反之,如果为了保证B进程正常使用,在A进程的DataTable设置为NULL后,使用GC.Collect(),这是个好的做法吗?

    我认为是不需要GC.Collect(),都为但是却无法具体说出理由。真心求详解。

  5. 老赵
    admin
    链接

    老赵 2014-05-15 21:24:04

    @咖啡色

    估计第一次只是没加载出来吧。

  6. 老赵
    admin
    链接

    老赵 2014-05-15 21:26:02

    @nihao

    这东西你自己试验下呗。

  7. 流光小弟
    121.231.163.*
    链接

    流光小弟 2014-05-16 17:34:27

    赵大哥,请教个问题,以前多次留言,您都没能看到,FluentValidation(验证框架),其利用表达式语法链式编程,喜欢表达式,喜欢链式的感觉, 但心中有一个疑问:IIS有个开启多工作进程的功能,一个进程管一块内存,相互独立,用这框架会不会有问题?

    // https://fluentvalidation.codeplex.com/
    public class OrdersValidator:AbstractValidator<Orders> 
    { 
        public  OrdersValidator() 
        { 
            RuleFor(orders => orders.CustomerID).NotEmpty().Length(2, 20).WithName("CustomerID"); 
            RuleFor(orders => orders.DisCount).GreaterThanOrEqualTo(0).LessThan(1).WithMessage("discount must between 0 and 1!"); 
            RuleFor(orders => orders.OrderDate.Date).GreaterThanOrEqualTo(DateTime.Now.Date).WithName("Order Date"); 
        } 
    }
    
  8. 老赵
    admin
    链接

    老赵 2014-05-17 00:24:11

    @流光小弟

    为什么你会觉得有问题,你是指哪方面问题?

  9. 链接

    Haiyang Zhu 2014-05-19 10:50:34

    PM不是project manager 么。。

  10. 流光小弟
    58.241.92.*
    链接

    流光小弟 2014-05-19 21:16:07

    程序的实现用到了委托链,这些东西应该是存在于内存中,IIS有个开启多工作进程,意味着程序有多个进程,每个进程的内存是独立的,那么会不会出现类似多个进程间无法共享状态信息(比如session),丢失找不到情况

  11. 老赵
    admin
    链接

    老赵 2014-05-20 10:45:53

    @流光小弟

    这就看你是怎么做的了,加入你要共享Session里的信息,但没有实现正确的话,那的确就会共享失败了。

  12. laobi
    124.205.175.*
    链接

    laobi 2014-05-20 15:25:43

    我怎么注册用户?

  13. 链接

    wei 2014-05-22 21:49:07

    Roslyn使用纯托管代码开发,但性能超过之前使用C++编写的原生实现。

    请给出证据,我怀疑Roslyn开发团队都不一定有。

  14. 老赵
    admin
    链接

    老赵 2014-05-24 23:37:50

    @wei

    找不到了,至少是内部邮件里说的。性能这么重要的东西,Roslyn团队怎么可能不做性能比较。别急,正式发布了以后各种消息会跟上的……

  15. maomao
    8.35.201.*
    链接

    maomao 2014-05-25 13:12:04

    不知咋滴,我这几天访问老赵的博客还要用goagent代理才行

  16. 老赵
    admin
    链接

    老赵 2014-05-25 21:50:29

    @maomao

    什么网络?

  17. 文少
    14.150.76.*
    链接

    文少 2014-05-26 09:52:46

    Roslyn使用纯托管代码开发,但性能超过之前使用C++编写的原生实现。

    这里说的性能是指编译器本身的性能(编译程序更快),还是编译出来的程序的性能(程序运行得更快)?

  18. 链接

    Ivony 2014-05-26 14:50:48

    http://zrx.zhaojie.me/20140515/ 这个文章下面的阅读原文链接到了这里,请检查一下是否链错了。

  19. 老赵
    admin
    链接

    老赵 2014-05-26 18:56:54

    @文少

    编译器本身执行的效率,不是编译出的代码的效率。

  20. 老赵
    admin
    链接

    老赵 2014-05-26 18:58:05

    @Ivony

    哦也你没看清内容…

  21. 链接

    wen zheng 2014-05-26 20:38:07

    我用的网络是联通的,在家里在公司都需要挂代理,但今天就没用,时好时坏,一阵阵的。

  22. mathgl
    1.36.236.*
    链接

    mathgl 2014-05-27 21:01:04

    java也提供unsafe 方法用来访问raw memory。有不少库用它来管理内存,减少gc heap的使用。

    不过这种用法不是写库的一般用不上。

  23. Kaka
    116.6.88.*
    链接

    Kaka 2014-07-07 13:42:49

    原来11年就开始看老赵的博客,后面没专注.NET就没看了,现在又来捧场了,请原谅我是个潜水族

  24. rxaa
    211.103.237.*
    链接

    rxaa 2014-07-10 09:49:25

    c++有基于模板构造于栈上的静态lambda闭包,性能不但无损,内联后还有提升,而且可以通过raii来代替gc以及所有资源的自动化管理. 可惜c#没有确定性析构功能,无法实现raii,而且gc也只能托管内存,很多情况下反而是个负担. 感觉语言都应该学学c++的基本原则:抽象不应该影响效率

  25. 永远的阿哲
    58.249.96.*
    链接

    永远的阿哲 2014-08-30 16:46:16

    老赵能否翻译《Pro .NET Performance》?我完完整整的听过您在WebCast上讲的Asp.Net Mvc 1.0,那是相当的好。期望您的vNext相关的教程

  26. 链接

    Ho Henry 2014-09-19 19:27:34

    .Net优化这个话题太大了,足够出本书了。感觉.Net开发效率很高,但如果是性能敏感的应用,处处是GC挖的坑。

    举个简单了例子:

    for (int i = 0; i < 1e6; i++)
    {
        foreach (var item in items)
        {
        }
    }
    

    这里面都有一个GC的坑。

  27. 老赵
    admin
    链接

    老赵 2014-09-21 21:28:40

    @Ho Henry

    什么坑?

  28. 黑耗子
    111.207.228.*
    链接

    黑耗子 2014-11-06 16:12:13

    FP手段是什么意思?

  29. linustd
    123.126.85.*
    链接

    linustd 2015-10-07 09:21:41

    要要要,怎么还写上Java了,不是说 Java 是一个 dead language 吗?

    自己闪自己嘴巴子,哈哈哈哈

    人家 UNIX 平台,技术都是沉淀了几十年,公开源代码让你研究学习的,某些贱货反而不学习研究。

    微软那破技术,垃圾一对,藏着掖着怕别人看见,某些贱货反而使出吃奶的劲偷窥。

  30. 老赵
    admin
    链接

    老赵 2015-11-02 06:46:50

    @linustd

    再怎么样也比你都快40了还学不会Java拿到月薪两万的工作还过不了试用期要强啊。

  31. 博客园
    60.29.148.*
    链接

    博客园 2015-11-25 15:40:55

    老大有没有如何在wcf中调用泛型方法的简单方案,不胜感激

  32. dearbaba
    192.250.192.*
    链接

    dearbaba 2016-01-24 14:00:17

    看了您的博文总结的非常好,您的博客非常不错。我也推荐一个程序员必备的搜索博客问答的网站 http://www.itdaan.com 。

  33. 广东硅谷学院
    120.237.57.*
    链接

    广东硅谷学院 2016-06-21 10:03:26

    广东硅谷学院#学好IT好就业选硅谷IT,学技能拿文凭事半功倍,紧跟专业教师一起冲浪IT行业。我们有建设学习型专业师资团队,教师领跑学生紧随其后。(QQ:800015777,电话0754-88989555)

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我