您的位置:知识库 » 软件工程

代码重构

发布时间: 2010-05-29 11:53  阅读: 14340 次  推荐: 2   [收藏]  

  五、重构与性能(Performance)

  译注:在我的接触经验中,performance一词被不同的人予以不同的解释和认知:效率、性能、效能。不同地区(例如台湾和大陆)的习惯用法亦不相同。本书一遇performance我便译为性能。efficient译为高效,effective译为有效。

  关于重构,有一个常被提出的问题:它对程序的性能将造成怎样的影响?为了让软件易于理解,你常会作出一些使程序运行变慢的修改。这是个重要的问题。我并不赞成为了提高设计的纯洁性或把希望寄托于更快的硬件身上,而忽略了程序性能。已经有很多软件因为速度太慢而被用户拒绝,日益提高的机器速度亦只不过略微放宽了速度方面的限制而已。但是,换个角度说,虽然重构必然会使软件运行更慢,但它也使软件的性能优化更易进行。除了对性能有严格要求的实时(real time)系统,其它任何情况下「编写快速软件」的秘密就是:首先写出可调(tunable)软件,然后调整它以求获得足够速度。

  我看过三种「编写快速软件」的方法。其中最严格的是「时间预算法」(time budgeting),这通常只用于性能要求极高的实时系统。如果使用这种方法,分解你的设计时就要做好预算,给每个组件预先分配一定资源 — 包括时间和执行轨迹(footprint)。每个组件绝对不能超出自己的预算,就算拥有「可在不同组件之间调度预配时间」的机制也不行。这种方法高度重视性能,对于心律调节器一类的系统是必须的,因为在这样的系统中迟来的数据就是错误的数据。但对其他类系统(例如我经常开发的企业信息系统)而言,如此追求高性能就有点过份了。
  第二种方法是「持续关切法」(constant attention)。这种方法要求任何程序员在任何时间做任何事时,都要设法保持系统的高性能。这种方式很常见,感觉上很有吸引力,但通常不会起太大作用。任何修改如果是为了提高性能,最终得到的软件的确更快了,那么这点损失尚有所值,可惜通常事与愿违,因为性能改善一旦被分散到程序各角落,每次改善都只不过是从「对程序行为的一个狭隘视角」出发而已。

  关于性能,一件很有趣的事情是:如果你对大多数程序进行分析,你会发现它把大半时间都耗费在一小半代码身上。如果你一视同仁地优化所有代码,90% 的优化工作都是白费劲儿,因为被你优化的代码有许多难得被执行起来。你花时间做优化是为了让程序运行更快,但如果因为缺乏对程序的清楚认识而花费时间,那些时间都是被浪费掉了。

  第三种性能提升法系利用上述的 "90%" 统计数据。采用这种方法时,你以一种「良好的分解方式」(well-factored manner)来建造自己的程序,不对性能投以任何关切,直至进入性能优化阶段 — 那通常是在开发后期。一旦进入该阶段,你再按照某个特定程序来调整程序性能。

  在性能优化阶段中,你首先应该以一个量测工具监控程序的运行,让它告诉你程序中哪些地方大量消耗时间和空间。这样你就可以找出性能热点(hot spot)所在的一小段代码。然后你应该集中关切这些性能热点,并使用前述「持续关切法」中的优化手段来优化它们。由于你把注意力都集中在热点上,较少的工作量便可显现较好的成果。即便如此你还是必须保持谨慎。和重构一样,你应该小幅度进行修改。每走一步都需要编译、测试、再次量测。如果没能提高性能,就应该撤销此次修改。你应该继续这个「发现热点、去除热点」的过程,直到获得客户满意的性能为止。关于这项技术,McConnell 【McConnell】 为我们提供了更多信息。
  一个被良好分解(well-factored)的程序可从两方面帮助此种优化形式。首先,它让你有比较充裕的时间进行性能调整(performance tuning),因为有分解良好的代码在手,你就能够更快速地添加功能,也就有更多时间用在性能问题上(准确的量测则保证你把这些时间投资在恰当地点)。其次,面对分解良好的程序,你在进行性能分析时便有较细的粒度(granularity),于是量测工具把你带入范围较小的程序段落中,而性能的调整也比较容易些。由于代码更加清晰,因此你能够更好地理解自己的选择,更清楚哪种调整起关键作用。
  我发现重构可以帮助我写出更快的软件。短程看来,重构的确会使软件变慢,但它使优化阶段中的软件性能调整更容易。最终我还是有赚头。

  优化一个薪资系统— Rich Garzaniti

  将Chrysler Comprehensive Compensation(克莱斯勒综合薪资系统)交给GemStone公司之前,我们用了相当长的时间开发它。开发过程中我们无可避免地发现程序不够快,于是找了Jim Haungs — GemSmith中的一位好手 — 请他帮我们优化这个系统。
  Jim先用一点时间让他的团队了解系统运作方式,然后以GemStone的ProfMonitor特性编写出一个性能量测工具,将它插入我们的功能测试中。这个工具可以显示系统产生的对象数量,以及这些对象的诞生点。
  令我们吃惊的是:创建量最大的对象竟是字符串。其中最大的工作量则是反复产生12,000-bytes的字符串。这很特别,因为这字符串实在太大了,连GemStone惯用的垃圾回收设施都无法处理它。由于它是如此巨大,每当被创建出来,GemStone都会将它分页(paging)至磁盘上。也就是说字符串的创建竟然用上了I/O子系统(译注:分页机制会动用I/O),而每次输出记录时都要产生这样的字符串三次﹗
  我们的第一个解决办法是把一个12,000-bytes字符串缓存(cached)起来,这可解决一大半问题。后来我们又加以修改,将它直接写入一个file stream,从而避免产生字符串。
  解决了「巨大字符串」问题后,Jim的量测工具又发现了一些类似问题,只不过字符串稍微小一些:800-bytes、500-bytes……等等,我们也都对它们改用file stream,于是问题都解决了。
  使用这些技术,我们稳步提高了系统性能。开发过程中原本似乎需要1,000小时以上才能完成的薪资计算,实际运作时只花40小时。一个月后我们把时间缩短到18小时。正式投入运转时只花12小时。经过一年的运行和改善后,全部计算只需9小时。
  我们的最大改进就是:将程序放在多处理器(multi-processor)计算器上,以多线程(multiple threads)方式运行。最初这个系统并非按照多线程思维来设计,但由于代码有良好分解(well factored),所以我们只花三天时间就让它得以同时运行多个线程了。现在,薪资的计算只需2小时。
  在Jim提供工具使我们得以在实际操作中量度系统性能之前,我们也猜测过问题所在。但如果只靠猜测,我们需要很长的时间才能试出真正的解法。真实的量测指出了一个完全不同的方向,并大大加快了我们的进度。

2
0
标签:重构

软件工程热门文章

    软件工程最新文章

      最新新闻

        热门新闻