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

如何编写高性能的应用程序2006-03-25 10:44 作者: 紫云英 出处: 程序员 责任编辑:方舟   何时应该做优化

  如果前一节所说的“不应优化”的前提不成立,那么就只能做优化了。具体的说:如果数据表明,性能确实没有达到指标,特别是当profiler表明,某处关键路径上的代码执行占用了大量的时间,那么就是优化的时候了。

  首先,要确保你要优化的代码是正确的,没有任何已知bug。因为优化后的代码往往会变得更复杂而难以修改,所以要趁代码还比较简单的时候赶紧把bug都修掉吧。

  然后,要确认性能指标,可以查specification,或者如果不清楚的话再问问客户,或者根据其他功能性需求计算得出。用profiler收集目前的性能数据,和性能指标对比,以确定是否需要优化、哪里需要优化。(数据要保留,因为等优化完后还要用这些数据来做对比,以检查优化是否有效。)常见的profiler有Rational Quantify、Borland Optimizeit等等。很多UNIX下面都自带了profiler,比如prof、gprof等,对于一般的使用已经够了。

  第三,进行优化。后面“常用的优化方法”一节对此进行了详细介绍。可以照着列出的常见的优化方法一个个地套用,或者更好的办法是进行一次团队头脑风暴会议,让大家提出各种可能的优化方案。记得优化时不要删除原来的实现。可以在源文件中以替代函数或者注释的方式保留原来的实现。

  第四,使用profiler,验证优化是否如所想的那样有效。如果有效,那是最好;如果无效甚至是帮了倒忙,那么就赶紧取消改动,使用原来的版本,然后继续尝试其他的优化方案。记得优化要一步一步来,从最省事且最有效的方案到最麻烦且收益最小的方案。一旦达成性能指标就收手,不要恋战。

  最后,记得对优化过的代码执行单元测试,看看有没有为了性能牺牲了正确性。要记得在注释或者文档中为优化留下记录。

  常用的优化方法

  最简单的优化:请检查是否使用了编译器的最新版本,是否把优化编译开关打开了,是否正确指定了目标处理器(以便使用MMX、SSE、3DNow!等高性能指令集以及让编译器自动为处理器所支持的其他高级特性做优化)。如果发布的产品要支持多种处理器,那么如果可能的话,请单独为每种处理器进行编译,分别发布,或者使用同一个发布包但让安装程序自动检测处理器型号并安装对应的二进制版本,或者把会在关键路径上执行的代码封装成动态链接库,然后让程序启动时自动检测处理器型号并加载为相应型号优化过的动态链接库版本。

  还有,要确保使用了高性能的库,好的算法。比如,同样是从堆上分配内存,不同编译器提供的malloc或者new的实现,性能差异就不小。GCC使用的DL malloc就比较高效,Borland的编译器提供的实现使用了类似内存池的方式来动态管理内存,效率也很高,但也有些编译器对此并没有做什么优化,直接进行系统调用。不仅malloc/new如此,STL的allocator也是如此。SGI STL带的allocator为小于128字节的内存块的分配进行了特别优化(用内存池实现),所以小型字符串以及其他会用到allocator此项功能的操作都会性能比较好,但其他STL实现就没有做这样的优化。

  选择正确的算法,往往比优化地实现算法更重要。因为不同时间复杂度的算法可能会给性能带来几个数量级的差异,而实现上的优化则往往付出很大、所得甚少。如果有时候精度不是那么重要,或者不需要找最佳的结果只需要找近似最佳的结果,那么往往可以用低时间复杂度的近似算法来代替。

  另外,查表法也是个常用的技巧。假设,用某个公式可以把彩色图像转换成灰度图象,那么如果转换处理量很大的话,对每个象素都用该公式计算一边就不划算了,完全可以事先对所有颜色都计算好,然后处理时查表即可。对三角函数也是如此。当然,为了减小表的尺寸,在精度上往往需要牺牲一些。

  但也不要以为因为是预先计算的不需要考虑计算代价,或者内存比较大虚拟内存更大,就可以把表做得很大。记住操作系统或者操作系统进行内存换页或者Cache换页都是要时间的,两个临界点分别是Cache的尺寸和物理内存的尺寸。具体是全部计算,还是全部查表,还是部分计算部分查表,表要做得多大,这些都需要尝试并用实际数据来支持。一个比较复杂的做法是动态地把计算出来的值缓存到稀疏表中并供以后使用时查询,表的物理尺寸根据当时机器的Cache、内存状况动态配置。

  如果使用Java或者.NET上的编程语言的话,因为垃圾会占用空间,垃圾收集器的执行会占用时间,所以除了优化算法及其实现,还要注意你的代码对垃圾收集器是否友好。比如有没有及时把不用的引用置成null,有没有不必要的finalizer等等。

  要避免很大的循环体,因为它们往往会超出Cache的尺寸。尽可能避免复杂的if-else或者switch-case语句,因为现代CPU的乱序执行功能看见这些语句会觉得很无奈。即便你非要用这些语句,最好养成习惯,把最可能的分支放在最前面。还有,如果可能的话,不要在循环体中使用这些条件分支语句。

  有一些经典著作,如The Practice of Programming(《程序设计实践》)、Programming Pearls(《编程珠玑》)、CodeComplete 2e(国内目前只出版了第1版,叫《代码大全》)也都提到了很多优化技术,但是,很重要的一点是,这些书都很少提到或者没有展开讲“构架设计时注意不要留下性能瓶颈或者缺陷”这个问题。这已超出了优化的范畴,而是要求在设计起始阶段时就考虑到性能需求。事实上,在硬件性能极大提高、优化编译器大行其道的今天,我们写程序时已基本上很少需要去考虑局部的微观实现是否优化了,因为有95%的可能编译器会替你去操心,或者根本性能不优化也可满足需求。甚至如果程序的内部结构比较清晰的化,算法也是可以很容易地替换的(比如用Strategy模式,或者Policy-Based Design的方式)。但也有的东西不太好在程序写完后再改,但又可能对性能有极大影响:那就是总体的设计和构架,以及一些影响面很广的设计决策/取舍。在今天,这些比较宏观的内容远比微观的优化技巧要重要。

  读者可能要问了:“不是说‘不必要的优化是一切罪恶的源泉’、‘没有数据证明就不要做优化’吗?在设计起始阶段根本还没有代码可以执行,怎么获得数据?你怎么保证这不会是不必要的优化呢?”噢,这个问题很好回答:当设计还没形成,代码还没写时,这不叫优化,仅仅是设计。优化是一种改变,把现有的缓慢的东西变成快速的东西。而设计时“本来无一物,何来谈优化”呢。

  更何况,一些比较宏观的构架上的决策,日后重构起来会非常困难,所以一开始就应该要考虑到。如果一开始需求尚未明确并且你也预计不到日后会有这样的性能需求,那么没有考虑到也不能怪你。但若一开始客户就提出了明确的性能要求,或者你心里很清楚客户一定会需要这样的性能,而你设计时却依然选择了无法或者难以满足这样性能要求的构架,那么这就不太好了。此外,如果两种设计/构架,并没有明确的实现复杂性或者优雅程度的差别,而其中一种设计/构架明显性能扩展性更好,那么也应该选择后一种。这不叫“premature optimization”,而叫做“避免premature pessimization”(见C++ Coding Standards一书的Item 9)。

  另外,还有一些很常见的和性能相关的话题。而且不少人对它们的认识还有一些误区,比如资源(特别是内存)的获取和释放、线程间的同步(也可看作特殊资源——各种线程锁的获取和释放)、字符串(或者其他缓冲区)的处理,以及这些操作的组合。这些话题很值得进一步讨论,在今后的文章中,会再和读者进行更深层次的交流。共2页。 9 7 1 2

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值