Effective Java 第三版——67. 明智谨慎地进行优化

Tips
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,因此JDK 最好下载 JDK 9以上的版本。html

Effective Java, Third Edition

67. 明智谨慎地进行优化

关于优化有三个格言,每一个人都应该知道:java

  • 更多的计算上的过失是以效率的名义(不必定实现它)而不是任何其余单一缘由——包括盲目作愚蠢的事情。
    ——William A. Wulf [Wulf72]
  • 咱们应该不去计较小小的效率,大约97%时间里:过早的优化是全部问题的根源。
    ———Donald E. Knuth [Knuth74]

在优化方面,咱们遵循两条规则:git

  • 规则1。不要优化。
  • 规则2(只适用于专家)。先不要优化——也就是说,直到你有了一个彻底清晰的还未优化的解决方案以前,不要优化。

全部这些格言都比Java编程语言的出现早二十年。 他们讲述了优化的深层真理:特别是如你过早优化的话,弊大于利。 在此过程当中,可能会生成既不快,又不正确,且没法轻松修复的软件。程序员

不要为了性能而牺牲合理的架构原则。努力编写好的程序,而不是快的程序。若是一个好的程序不够快,它的架构容许对其进行优化。好的程序体现了信息隐藏的原则:在可能的状况下,他们设计决策本地化为单个组件,所以能够在不影响系统其他部分的状况下更改单个决策(条目15)。github

这并不意味着能够在程序完成以前忽略性能问题。 实现问题能够经过之后的优化来解决,可是若是不重写系统,就没法修复限制性能的广泛存在的架构缺陷。 过后改变设计的基本方面可能致使结构不良的系统难以维护和发展。 所以,必须在设计过程当中考虑性能。算法

尽可能避免限制性能的设计决策。设计中最难以更改的组件是那些指定组件之间以及与外部系统的交互的组件。这些设计组件中最主要的是API、线路层(wire-level)协议和持久化数据格式。这些设计组件不只难以或不可能在过后更改,并且全部这些组件均可能对系统可以达到的性能形成重大限制。编程

考虑API设计决策的性能影响。 使公共类型可变可能须要大量没必要要的防护性拷贝(条目 50)。 相似地,在一个公共类中应该使用复用更为合适,但依旧使用继承会把该类永远绑定到它的父类,这会人为地限制子类的性能(第18项)。最后一个例子是,在API中使用实现类型而不是接口会把你绑定到特定的实现,即便未来可能会编写更快的实现(条目 64)。架构

API设计对性能的影响是很是真实存在的。 考虑java.awt.Component类中的getSize方法。 这个性能关键方法决定,是返回Dimension实例,并且Dimension实例是可变的,强制此方法的任何实现都在每次调用时分配一个新的Dimension实例。 尽管在现代VM上分配小对象的成本很低,可是没必要要地分配数百万个对象会对性能形成实际损害。框架

存在几种API设计替代方案。 理想状况下,Dimension应该是不可变的(条目 17); 或者,getSize可能已被两个返回Dimension对象的各个基本组件的方法所代替。 实际上,出于性能缘由,在Java 2中将两个这样的方法添加到Component类中。 可是,预先存在的客户端代码仍然使用getSize方法,而且仍然会受到原始API设计决策的性能影响。编程语言

幸运的是,一般状况下,好的API设计与好的性能是一致的。为了得到良好的性能而包装API是一个很是糟糕的想法。致使包装API的性能问题可能在平台或其余底层软件的将来发型版本中消失,可是包装API和随之而来的支持问题将永远伴随着你。

一旦仔细设计了程序并生成了清晰,简洁且结构良好的实现,那么多是时候考虑优化,假设你对程序的性能还不是不满意。

回想一下Jackson的两条优化规则是“不要优化”和“(只针对专家)仍是先别优化”。他本能够再加上一条:在每次尝试优化以前和优化以后,要测量性能。你可能会对本身发现感到惊讶。一般,尝试的优化对性能没有可测量的影响;有时候,他们让事情变得更糟。主要缘由是很难猜想程序将时间花在哪里。程序中你认为很慢的部分可能并无错,在这种状况下,浪费时间来优化它。通常认为,程序将90%的时间花在10%的代码上。

性能分析工具能够帮助你决定将优化工做的重点放在哪里。这些工具提供了运行时信息,好比每一个方法大约花费多少时间以及调用了多少次。除了关注调优工做以外,还能够提醒你须要进行算法更改。若是程序中潜藏着平方级(或更糟)算法,那么再多的调优也没法解决这个问题。必须用一个更有效的算法来代替这个算法。系统中的代码越多,使用分析工具就越重要。这就像大海捞针:大海捞针越大,金属探测器就越有用。另外一个值得特别说起的工具是jmh,它不是一个分析工具,而是一个微基准测试框架,提供了非并行的可见对Java代码的详细性能 [JMH]。

与C和C++等更传统的语言相比,Java甚至更须要度量尝试优化的效果,由于Java的性能模型很弱:各类基本操做的相对成本没有获得很好的定义。程序员编写的内容和CPU执行的内容之间的“抽象鸿沟(abstraction gap)”更大,这使得可靠地预测优化的性能结果变得更加困难。有很关于性能的说法流传开来,但最终被证实是半真半假或彻头彻尾的谎话。

Java的性能模型不只定义不清,并且在不一样的实现之间、不一样的发布之间、不一样的处理器之间都有所不一样。若是要在多个实现或多个硬件平台上运行程序,那么度量优化对每一个平台的效果是很重要的。有时候,可能会被迫在不一样实现或硬件平台上的性能之间进行权衡。

自本条目首次编写以来的近20年里,Java软件堆栈的每一个组件都变得愈来愈复杂,从处理器到不一样的虚拟机再到类库,Java运行的各类硬件都有了极大的增加。全部这些加在一块儿,使得Java程序的性能比2001年更难以预测,而对它进行度量的需求也相应增长。

总而言之,不要努力写出快速的程序——努力写出好的程序; 这样速度将随之而来。 可是在设计系统时要考虑性能,尤为是在设计API,线级协议和持久化数据格式时。 完成系统构建后,请测量其性能。 若是它足够快,你就完成了。 若是没有,请借助分析工具找到问题的根源,而后开始优化系统的相关部分。 第一步是检查算法选择:再多低级优化也不能够弥补差的算法选择。 根据须要重复此过程,在每次更改后测量性能,直到满意为止。

相关文章
相关标签/搜索