性能优化的方法和技巧:总结

Sina WeiboBaiduLinkedInQQGoogle+RedditEvernote分享




性能优化这个话题可大可小。从大的方面来说,在系统设计之初,需要考虑硬件的选择,操作系统的选择,基础软件平台的选择;从小到方面来说,每个子系统的设计,算法选择,代码怎么写,如何使用编译器的选项,如何发挥硬件的最大性能等等。本系列关注的是在给定硬件平台,给定操作系统和基础软件平台的情况下,如何优化代码,简而言之,就是关注小的方面。

在给定硬件平台的情况下,如何发挥硬件的最大效能?前提是需要对硬件平台很熟悉。如何分离硬件相关代码和硬件无关的代码是一个很重要的技能。针对某一硬件平台优化固然是好,但是如果代码都是硬件相关的,就失去了可移植性,软件的性价比不一定高。

性能优化只是系统的一个方面,它会和系统的其他要求有冲突,比如

  • 可读性:性能优化不能影响可读性,看起来不怎么漂亮的代码,没有人愿意维护
  • 模块化:性能优化往往需要打破模块的边界,想想这是否值得
  • 可移植:隔离硬件相关的代码,尽量使用统一的API
  • 可维护:许多性能优化的技巧,会导致后来维护代码的人崩溃

需要在性能优化和上述的几个要求之间做出tradeoff,不能一意孤行。

性能优化的目标是什么?不外乎两个:

  • 时间性能:减小系统执行的时间
  • 空间性能:减小系统占用的空间

那么我们优化的策略,首先就是对系统进行分解,测量:

  • 分解:系统包含的子系统,执行路径,函数,指令等等
  • 测量:每个子系统,执行路径,函数,指令所花费的时间和空间

然后,选取执行次数最多,消耗时间最长的函数进行优化(这里需要指出的是,消耗时间最长的函数并不一定就是优化的对象,这个需要放到某个执行路径上去考察,优化最常使用的执行路径)。

对于单核和多核的优化,有什么不同?以cache miss为例,在单核上

  • I-cache miss的原因是什么?一是代码路径太长,以至于超出了cache的容量,cache需要替换;二是        多个执行路径之间相互交替,cache需要替换;三是I-cache, D-cache互相踩。(对于i-cache的优化,基本上没有什么可遵循的规则。最有效的还是:一,减少无用的代码;二,减少冗余的代码;三,减少函数调用的开销;4,快速路径短小精干,相关代码相邻;5,相关代码放到一个段里面等等)
  • D-cache miss的原因是什么?一是数据结构太大,超出了cache的容量(大数组,长链表都会有这个问题,比较之下,还是数组更让人放心一点);二是多个数据结构之间相互踩(问题是一个执行路径,需要访问那么多数据吗?如果是,这个路径是不是太复杂了一点);三是D-cache和I-cache的冲突(这个不好说,系统里面会出现这种模式的cache miss吗?)

在多核上,cache miss会有什么新的特点?

  • I-cache miss:多核有独立的L1 cache,共享的L2/L3 cache。如果多条执行路径同时进行,cache的冲突几率就会增大。但是这是没办法的事情,总不能什么事都不做吧。所以,对i-cache的优化,关注快速路径就可以了,不要把精力都花费在这个上面
  • D-cache miss:锁,原子操作,伪共享等都会引起多核上的D-cache miss。这是多核优化时需要关注的重点。优化的策略也很直接,就是尽量减少锁,原子操作,伪共享等。多核的所有冲突都是由共享引起的,所以要区分出哪些是必须共享的,哪些是可以per thread的。并行优化与应用有关,需要注意的是,优化是否对所有用例都有效?做不好,可能顾此失彼。

性能优化必备技能

  • 熟悉系统执行路径:可以通过读代码或者使用profiling工具学习代码,要思考执行路径上不合理的地方,看看哪里可以减少,哪里可以合并。熟悉系统执行路径是性能优化的基础。
  • 熟悉测量工具:顺手的工具必不可少
  • 常用的代码优化技巧和策略:针对不同的语言,不同的平台,使用与之相应的技巧和策略

学习性能优化是一个不断积累的过程,在这个过程中,总结和学习自己用的顺手的工具和技巧,不断尝试,不断思考,就会有收获(感谢teltalk.org读者的评论,弯曲评论的精华在评论,思想碰撞才有火花)。

参考资料:

1: http://en.wikipedia.org/wiki/Program_optimization

2: http://www.agner.org/optimize/

3: Code Optimization – Effective Memory Usage

4: Hacker’s Delight

(3个打分, 平均:3.67 / 5)

雁过留声

“性能优化的方法和技巧:总结”有43个回复

  1. multithreaded 于 2011-05-23 9:02 上午

    写的很好,但有一点值得注意:

    〉性能优化的目标是什么?不外乎两个:

    ■时间性能:减小系统执行的时间
    ■空间性能:减小系统占用的空间

    我认为你现在所谈的都是有关时间方面的性能优化。 应该直接了当的说:要想减少执行时间, 就应该。。。

    如何减小系统占用的空间是另外一套方法。

  2. 理客 于 2011-05-23 10:15 上午

    确是心得!

  3. Tony 于 2011-05-23 10:48 上午

    (sorry or using English here, I have no Chinese input at office)

    Great article !
    but, I do not understand the following point “D-cache miss:锁,原子操作,伪共享等都会引起多核上的D-cache miss”

    I understand Lock latency is quite high, several hundreds of cycles at SUN server. but, why would it cause cache miss ?

    would anybody please give more detail here ?
    Thank you very much :)

  4. kevin 于 2011-05-23 1:59 下午

    lock is a special memory access, if you look into the lock implementation code

  5. ssxiaohe 于 2011-05-23 6:56 下午

    非常欣赏kenelchina同学的专业精神和无私奉献,一直都很关注你的文章,受益匪浅。不知能否从大的方面论述下性能优化呢?

    “从大的方面来说,在系统设计之初,需要考虑硬件的选择,操作系统的选择,基础软件平台的选择”

  6. 陈怀临 于 2011-05-23 8:04 下午

    Tony,关于锁,特别是spinlock在multicore上的分析,大概是这样,这样的。

    你的问题很不错。感觉是个很sharp的年轻人。

    Kernelchina在D-Cache Miss里去emphasize(强调)锁机制是不precise的,if I don’t say he should NOT.

    锁机制的难度不是锁本身,而是锁的粒度(Granularity)。例如,Big Lock问题。。。

    在锁的本身方面,有许多做法。我在工作中拿mem做过,拿chipset的register做过。也有许多NPU会设计许多锁。效果都差不多。高端CPU最后都是Cache-Cache的sync memory变量。

    其实,这些latency最后都不是killer。杀死一个系统的是legacy system对细化锁粒度的局限。

    换言之,革命容易,改革难。

  7. kernelchina 于 2011-05-23 8:21 下午

    首席能不能把这个系列归纳一下,做个序,评论什么的,这样读者可能会更有感觉一点,就像Pattern系列。对于cache miss部分,有了测量数据,有时就是想不明白原因是什么,也行想的方向有点问题。

  8. 陈怀临 于 2011-05-23 8:26 下午

    看在你的面子上,我答应。。。

  9. 理客 于 2011-05-23 10:39 下午

    所以维新失败,革命成功,但伤害巨大;所以手术容易,中医很难,所以我们现在的改革其实很难很难

  10. 三千大千世界 于 2011-05-25 8:09 上午

    首席,kernel不就是用的大内核锁吗?难道也是某种trade off的结果?

  11. 三千大千世界 于 2011-05-25 8:32 上午

    看来是我理解错了,大内核锁是历史遗留问题,2.6内核用得也不多。内核里面的锁很多,我这个阶段用锁还停留在不要导致死锁上,还未意识到锁对性能的影响。理论上锁的粒度应该越细效率越高吧?但是粒度太细了会引入复杂度,确实很难trade off。
    刚刚查了以下,有个lockmeter的工具可以度量在multiprocessor上的linux kernel的spin lock对性能的影响,回头找个时间研究一下。

  12. 一条虫 于 2011-05-25 6:42 下午

    GNU一直认为应该在某一天替换掉linux,而使用微内核的HURD之类。因为他们也不喜欢宏内核

  13. kernelchina 于 2011-05-25 6:52 下午

    理想是理想,现实是现实,如果linux一开始就走微内核的路,估计和hurd差不多的命运。

  14. 一条虫 于 2011-05-28 9:31 下午

    我不这么认为。linux没走微内核不是对其生存考虑的。而是linus根本没那个觉悟。再者后期开发是绝对的低技术人力密集型(相对而言),所以不再乎。到后来商业操作系统压榨下的厂商将linux作为战略投资,linux在技术各方面才开始有起色。

  15. 陈怀临 于 2011-05-28 9:46 下午

    linux没走微内核,不是在胶片的基础上,在那冰天雪地的北欧,经过研发部长们的沙盘推演而得出的智慧决定。而是Linus在那个时候(估计现在也一样),其实就根本不懂Micro-Kernel。他当初也就一个大四的学生。。。

    这就是bottom up的魅力。。。自由发展:-)。

  16. westermann 于 2011-05-28 10:36 下午

    嗯?linus应该懂微内核吧?那厮不是还为微内核宏内核谁更叼和写minix的那个知名教授论战过么?

  17. 一条虫 于 2011-05-28 11:14 下午

    那是被Tanenbaum戳了以后打嘴仗需要恶补的吧。lol

  18. 理客 于 2011-05-29 2:12 上午

    不懂linux,如果从前面内行的评论看,linus也不过尔尔,没有他,也会有别的xx出来,甚至更好,如果和ZZ横向比较,也就和老毛TG能得天下一个level

  19. shuyong 于 2011-05-29 3:28 上午

    其实也没有这么糟糕了。linux的成长,也不是linus一个人的功劳。随着linux的发展,包括linus在内的参与其中的人都得到了发展。这大概算是草根的力量吧。

    那次关于微内核的争论,我看是工程界的一次胜利。当时微内核有两个不利的问题:一是当时所有的微内核项目都暴露了性能的问题;二是因为前面的问题,没有多少软件想移植到微内核系统上,因而缺乏应用。至于大内核难以维护难以扩展的问题,凭着大市场的开发模式,凭着蚁多咬死象的战术,linux这些年也跌跌撞撞地发展到今天这样庞大的规模。现在L4 OS据说解决了性能的问题。但要证明这个问题,就必须要大量的应用在上面运行测试。但要是移植了应用过来,如果性能还是不行,那这移植工作就是无用功了,谁也不想干。这鸡和蛋的问题,错过了时机,就成了无解的问题。

    所以性能问题还真是开发的头等大事。一力降十会,野蛮才能生长。

  20. 一条虫 于 2011-05-29 4:56 上午

    早就解决性能问题了……在process id硬件出来的那一天。主流商业系统哪个不是micro啊。不是mciro..人力成本伤不起啊。。伤不起。像linux那样公司玩不得亏成非洲啊。

  21. 理客 于 2011-05-29 5:10 上午

    外行人看到的,似乎从服务器到网络设备,大部分都是基于Linux,为什么不用micro呢?还是Linux也micro了?

  22. 一条虫 于 2011-05-29 5:41 上午

    it’s free… lol

    至于linux的用途……F35不用 高档汽车不用 医疗器器械不用 波音NASA不用 思科,Juniper也没用。。能打星际的示波器也不用。

    可能……只有服务器和一些网络设备用。还有巨型计算机

  23. shuyong 于 2011-05-29 7:42 上午

    linux不是micro kernel,虽然它从微内核那里借鉴了很多思想。

    micro kernel有一个特征是不同的模块运行在不同的地址空间,它们之间需要通过message交换数据。除了message system,其它模块都可以运行在用户空间。这样能造成系统死机的模块就很小,就容易维护。其它模块死了,就还可以再次启动,这样整个软件系统就足够坚强。

    相似的思想在其它地方应用得很多。例如MS DCOM,或者是linux上的DBUS。这种系统的性能受制于message system的性能。像L4 OS,就是对mailbox指令直接绑定到汇编指令以提高性能。

    linux的所有模块都运行在同一个地址空间,包括后来动态加载的模块。只要有一个模块死了,大家都死。linux的可维护性是通过不断修改接口,抽象接口来提高的。但是要死一块死的缺点,是大内核无法改变的问题。

    但是linux系统也在尝试用户空间驱动,在USB系统那块应用得最多。只不过没有特别的需求,大家都没有动力把驱动移植到用户空间了。所以,如果不从原教义的角度看问题,linux可以做成micro kernel,就看有没有需求了。L4 OS在尝试进入虚拟化的领域,从更大的角度看,Linux / Window Mobile / WebOS,只不过是更大一点,更容易崩溃一点的TASK罢了。所以,改良,总不会呈现令人激动的颠覆时刻。只有方向正确,只要时间足够,事情总会向着互相融合,互相共存的方案演变。

  24. 一条虫 于 2011-05-29 5:32 下午

    er…DBUS好像不是linux上的吧。。。如果说借鉴了micro kernel应该是指那个可加载模块?这个就有点幽默了。

    难道linux未来的方向就和Intel CISC演化类似?微内核真核心,模拟的宏内核表现?

  25. kernelchina 于 2011-05-29 7:11 下午

    内核模块的好处在于升级方便吧,虽然很多人还没意识到这个好处,主要原因是linux没有稳定的api。

  26. anonymous 于 2011-05-29 7:42 下午

    不是最好的就能发展起来,而是要看历史选择了谁。

  27. shuyong 于 2011-05-29 7:59 下午

    DBUS的例子只是说明它们之间的设计思路是相通的。server / client都运行在不同的地址空间,之间通过消息传递数据,少了谁系统照样跑。

    动态加载模块不算micro kernel的特点。但用户空间的驱动应该是。应该说,有了用户空间驱动的机制,linux就有了变成micro kernel的可能。但现在大家都没有动力往这方面发展,因为现在的问题用现在的技术和工程方法都能cover住,多一事不如少一事。

  28. 陈怀临 于 2011-05-29 8:35 下午

    我在前面的文章或者评论中谈过:OS最新的发展就是:虚拟化技术。虚拟化技术会使得各种OS的界限和谐化。

  29. 理客 于 2011-05-29 11:05 下午

    学习了。
    Linux做不到不同模块/进程用不同的地址空间,隔离故障吗?
    Linux和micro在性能上大体有多大差距?

  30. Lucifer 于 2011-05-30 1:02 上午

    linux kernel就是一打个可执行映像么……单内核就是这样,因为是同一个进程,kernel内的调用通过内存映射之类的方法就可以,而microkernel的就要通过message来进行

  31. shuyong 于 2011-05-30 1:07 上午

    在linux kernel space,所有的模块都是运行在同一个地址空间,各自都可以通过函数调用访问接口,可以直接访问数据。动态模块加载,实质上是ELF loading,把所有的符号表解决了,后来的模块就可以直接调用原有模块的函数。这种设计思想是相信开发者的能力而简化了设计。出了故障,那只有kernel panic了。

    micro kernel的性能,是个热门的话题。有个L4linux的项目,号称经过L4的改造,linux系统比原来要快。但是除开实验室里的人,有几人敢实际用到产品里?

  32. 理客 于 2011-05-30 1:12 上午

    shuyong:这里模块的概念是指一个进程里的不同模块,还是不同的进程?还是一个模块同时也包含了多个进程?

  33. Lucifer 于 2011-05-30 1:27 上午

    早期的microkernel似乎性能耗费在20%~30%吧?现在的l4据说可以做到5%~10%

  34. 瞎扯 于 2011-05-30 1:58 上午

    Linus 当时怎么可能不知道micro kernel? 不是整个世界都和咱们的网络一样封闭。 Linus 91年出第一版就说了是山寨 Minix,第二年初,也就92年就和反动权威Tanenbaum在新闻组里单挑,论战头条就是Microkernel vs Monolithic System。 Tanenbaum可能是研究搞多了一根筋,到了2006年,形势都已经明朗的不能再明朗了,还要跳出来自己找打。纯micro比较流行的只有QNX吧,NT及以降/OS X都算是hybrid。

    另外,我理解的虚拟化难道不是为了(共享|分配|兜售)硬件资源么?OS界限的和谐化这种工作恐怕还是交给java们去做吧。

  35. 理客 于 2011-05-30 2:04 上午

    to Lucifer:也就是micro性能会降低,但可靠性大大提高?

  36. shuyong 于 2011-05-30 2:18 上午

    我所说的模块不是一个严格的定义,只是大家约定俗成的一种说法。所说的模块,都是指linux的一个可以分割的组成部分。

    从动态转载的角度,linux可以划分为一个巨大的开机后就在内存的部分,包括调度/MMU/底层硬件接口等部分,还有多个可以动态加载的模块。这些模块大多是设备驱动,也有部分是和硬件不紧密的功能模块,例如ppp protocol。

    这些模块都运行在kernel space,有kernel thread的概念。所有的user space的程序有process id >= 1。所以我们也可以认为模块是在process id = 0里面的众多threads。

  37. 理客 于 2011-05-30 2:51 上午

    明白了,是kernel里的模块,非用户态的模块。个人感觉对军工航天航空等一些对可靠性超过6西格玛的,应该micro更好,对民用,包括电信IT等,不一定需要micro,除非非micro的OS搞得很差,必须要micro来解决,因为系统可靠性概念不单单是内核,要E2E,这里有木桶原理,你把一片做得很长很长有两个问题,一个是成本,一个是效果,具体处理参考首席trading off理论

  38. kernelchina 于 2011-05-30 3:25 上午

    linux module相当于动态库,不能独立运行。

  39. westermann 于 2011-05-30 5:35 上午

    》》》我在前面的文章或者评论中谈过:OS最新的发展就是:虚拟化技术。虚拟化技术会使得各种OS的界限和谐化。

    首席的意思是说,课本上的os将消失,将向两个方向进化,一个是现在虚拟机的hypervisor(负责协调硬件资源),另一个就是application aware os?

  40. 一条虫 于 2011-05-30 6:23 上午

    hybrid是在早期的硬件上折中方案。现在已经没有什么关系了。只不过成型的东西不想动而已。所有商业系统都扯上micro,足以说明微内核理念在节省人力和增强稳定性上的功效。

    linus早期也就是想山寨一个unix,借鉴了minix的文件系统而已。至于懂不懂那就很难说了。

    看来虚拟化还是很有前景的,去看看。谢首席方向性指引lol

  41. Lucifer 于 2011-06-02 2:48 下午

    几天没来了哦……

    to 35。是这样

  42. awei 于 2011-06-16 10:12 下午

    看一看 UNIX内核程序员的SMP 和cache 一直性
    这本书, 这个帖子就可以结了

  43. 三千大千世界 于 2011-06-22 6:44 上午

    42楼,此言差矣!
    如果我没有记错的话,记得两三年前,园区的国际科技园内就家做手机搜索的,不知道现在咋样了。苏州距离南京、上海、杭州都不算远,只要机会合适或者离家近,到苏州的同学还是大大的有的 :)