如何编写高性能的应用程序1-开发者网络-优化-天极Yesky

如何编写高性能的应用程序2006-03-25 10:44 作者: 紫云英 出处: 程序员 责任编辑:方舟   在这个硬件性能日新月异的年代里,我们充分的享受着硬件带给我们的“免费午餐”,似忽软件的效率问题已经不再是一种被开发者关心的问题,但是,随着CPU的主频提高步伐越来越慢,高速缓存也不是可以无限制增加,新技术的使用逐渐被用来提高硬件的性能,这时候,要充分发挥这些新特性,软件的支持就显得很重要了,于是,程序的运行效率再一次映入我们的视野,新时代的软件优化,我们该何去何从?

  看见这篇文章的标题,你可能会说:噢,有必要吗,现在的电脑速度越来越快,何苦去费力写高性能的程序呢?如果程序性能实在无法恭维,满足不了需求,那大不了去换一块更快的CPU,问题就可以轻松解决了。

  或许曾经如此,但种种迹象表明,硬件业为软件业提供的免费“性能提升”午餐已经快要到头了,CPU的主频提高步伐越来越慢,高速缓存也不是可以无限制增加,现在CPU更多地用新指令集、并行执行、乱序执行、多处理内核等技术来提高速度,而这些技术都需要软件的配合才能发挥作用。如果软件没有使用MMX、3DNow、SSE等指令集,那么就无法享受这些指令集带来的性能提升;如果程序内部并行性不好(没有使用多线程构架、执行流中条件转移过多),那么就无法高效利用CPU的并行处理功能(多内核、乱序并行执行)。所以,“如何编写高性能程序”还是一门需要仔细钻研的学问。

  幸运的是,这些“需要软件的配合”的工作大多数可以由编译器来自动完成。所以绝大多数情况下我们不需要也不应该牺牲程序的可读性去做局部优化。本文的“何时不应该做优化”一节对此进行了详细讨论。

  但是,也有很多优化,特别是较大粒度、较高抽象层次的优化,至少在目前,编译器是无法做到的。比如,把单线程构架改成多线程构架(这点非常重要,否则哪怕运行在四内核的处理器上你的程序也只能占用一个内核,而现在Intel、AMD、IBM都把在处理器上集成多内核当作了主要的提升处理器性能的手段),使用较低时间复杂性的算法,等等。本文的“何时应该做优化”以及“优化的步骤”两个小节对此做了展开叙述。

  涉及到较高层次的优化,以及系统构架对性能的影响,其实有很多规律可行。本文的“设计构架时要注意的一些原则”和“性能模式与反模式”两小节对此进行了叙述。

  在开发中,我们最经常遇到的优化问题是:内存分配(在大多数操作系统中,从堆上分配内存是开销比较大的操作),线程同步(不同的同步原语性能差距很大),字符串操作(STL中的string真的要比char*慢吗,Copy-on-Write的string是提高了还是降低了性能),以及这3个问题的组合(在多线程环境下进行涉及到内存分配的字符串操作)。

  本文也比较详尽地探讨了这些常见的问题。

  何时不应该做优化

  用3句话来概括:如果没有数据证明“性能没有达到指标”,就不要做优化;如果待优化部分不是在关键路径上,就不要做优化;如果没有数据证明“优化确实起作用了”,就不要使用优化的代码。

  这3句话中的“数据”指的主要是用profiler模拟各种真实或者极限情况下跑出的数据。背后的理念是,要有的放矢,功夫花在刀刃上(注意80/20法则),用数据说话,不要想当然。

  比如,若你参与开发的一个软件,测试时发现启动和退出花的时间都比较长,在代码复审时也确实发现,启动和退出部分的代码不够高效。于是,放弃休假时间,终于把这部分优化好了。嗯,且慢,再看一下需求。噢,这个软件会被这样用:启动之后就会24x7地不停机地运行,直到两年后被新版本代替。那么就是说,启动和退出代码只会被执行一次。你放弃休假时间所做的优化值得吗?优化之前请先检查代码是否在关键路径上。

  再比如,你发现代码中有一些小函数,还有一些循环体,想优化一下,就把小函数都内联了,把循环体展开了。这时你就必须用数据来证明,你的优化确实起作用了,而没有“好心帮倒忙”地让优化起反作用。你可能会惊讶,“怎么可能”?噢,完全有可能的,因为可能原来的这部分代码比较小,正好够放在CPU的一级高速缓存中,或者够放在物理内存中。你优化过后代码体积增加了,于是缓存中放不下了,或者甚至内存都放不下要用虚存了,于是CPU或者操作系统不得不经常换页,性能会大幅度降低。所以,不要想当然,要用数据证明你所做的优化确实是优化。

  还有很多“好心没好报”、“优化反被优化误”的例子。比如很多编译器都会自动识别对数组操作的for循环这样的代码模式,并自动做优化。如果你多事地自己去做了优化,改成了指针操作,编译器可能就不认识这个代码模式了,于是就只好保守地产生非优化的代码。这样产生的代码往往反而效率降低了。

  再比如现在很多JVM都会识别String操作的模式。若你为了可能的性能提升而手工地把对String的操作改成不那么直观优雅的StringBuffer操作,那不仅降低了代码的可读性,还给JVM的优化机制帮了倒忙。

  再比如,Copy-On-Write是一种常用的对字符串操作的优化,可以大幅度减少内存分配和读写操作。但是在多线程环境中,为了保持Copy-On-Write的正确性,就不得不额外增加不少线程同步操作。究竟是内存操作的开销大还是线程同步操作的开销大呢?最好用数据来说话。事实上,在多线程环境中Copy-On-Write多半是个得不偿失的优化。以前不少STL实现中string都用了Copy-On-Write,但现在好多STL实现都摒弃了这种做法。其实我觉得更好的办法是提供参数来让用户选择。毕竟如果不需要用到多线程,Copy-On-Write还是很好的。

本文转自
http://dev.yesky.com/278/2347278.shtml
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值