Effective Java 第三版——67. 明智谨慎地进行优化
Tips
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。

67. 明智谨慎地进行优化
关于优化有三个格言,每个人都应该知道:
- 更多的计算上的过失是以效率的名义(不一定实现它)而不是任何其他单一原因——包括盲目做愚蠢的事情。
——William A. Wulf [Wulf72] - 我们应该不去计较小小的效率,大约97%时间里:过早的优化是所有问题的根源。
———Donald E. Knuth [Knuth74]
在优化方面,我们遵循两条规则:
- 规则1。不要优化。
- 规则2(只适用于专家)。先不要优化——也就是说,直到你有了一个完全清晰的还未优化的解决方案之前,不要优化。
所有这些格言都比Java编程语言的出现早二十年。 他们讲述了优化的深层真理:特别是如你过早优化的话,弊大于利。 在此过程中,可能会生成既不快,又不正确,且无法轻松修复的软件。
不要为了性能而牺牲合理的架构原则。努力编写好的程序,而不是快的程序。如果一个好的程序不够快,它的架构允许对其进行优化。好的程序体现了信息隐藏的原则:在可能的情况下,他们设计决策本地化为单个组件,因此可以在不影响系统其余部分的情况下更改单个决策(条目15)。
这并不意味着可以在程序完成之前忽略性能问题。 实现问题可以通过以后的优化来解决,但是如果不重写系统,就无法修复限制性能的普遍存在的架构缺陷。 事后改变设计的基本方面可能导致结构不良的系统难以维护和发展。 因此,必须在设计过程中考虑性能。
尽量避免限制性能的设计决策。设计中最难以更改的组件是那些指定组件之间以及与外部系统的交互的组件。这些设计组件中最主要的是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,线级协议和持久化数据格式时。 完成系统构建后,请测量其性能。 如果它足够快,你就完成了。 如果没有,请借助分析工具找到问题的根源,然后开始优化系统的相关部分。 第一步是检查算法选择:再多低级优化也不可以弥补差的算法选择。 根据需要重复此过程,在每次更改后测量性能,直到满意为止。
Effective Java 第三版——67. 明智谨慎地进行优化的更多相关文章
- Effective Java 第三版——83. 明智谨慎地使用延迟初始化
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- Effective Java 第三版——66. 明智谨慎地使用本地方法
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- Effective Java 第三版——45. 明智审慎地使用Stream
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——52. 明智而审慎地使用重载
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- Effective Java 第三版——55. 明智而审慎地返回Optional
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- Effective Java 第三版—— 86. 非常谨慎地实现SERIALIZABLE接口
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- Effective Java 第三版——53. 明智而审慎地使用可变参数
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- 《Effective Java 第三版》目录汇总
经过反复不断的拖延和坚持,所有条目已经翻译完成,供大家分享学习.时间有限,个别地方翻译得比较仓促,希望有疑虑的地方指出批评改正. 第一章简介 忽略 第二章 创建和销毁对象 1. 考虑使用静态工厂方法替 ...
- 《Effective Java 第三版》新条目介绍
版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...
随机推荐
- unity打成aar上传到maven库的工具
需求: 把unity打成aar并上传到maven库 其实就是把前两个博客整合了一下 unity打aar包工具 aar上传maven库工具 这里先说eclipse版的 package com.jinke ...
- 一、利用Python编写飞机大战游戏-面向对象设计思想
相信大家看到过网上很多关于飞机大战的项目,但是对其中的模块方法,以及使用和游戏工作原理都不了解,看的也是一脸懵逼,根本看不下去.下面我做个详细讲解,在做此游戏需要用到pygame模块,所以这一章先进行 ...
- poj3311
poj3311我写的第一篇状压dp,调试了好久23333,其实状压就是二进制结合位运算,把整体的状态用二进制来表示,一般是0/1,用没用过或者走没走没走过的问题,其实这种思想在搜索中也有涉及过,与dp ...
- ssh框架中.xml文件小技巧分离xml
struts.xml文件 struts.xml文件里的action可以分离出来,如: <!-- 预警信息监测 --> <include file="config/strut ...
- 【C#】Convert.ToInt32、(int)和int.Parse三者的区别
前者适合将object类类型转换成int类型 (int)适合简单数据类型之间的转换: int.Parse适合将string类类型转换成int类型.
- sql之Replace
update [ConfigDb].[dbo].[RuleParams] set RuleName=REPLACE(RuleName,'业务','') where (RuleId>358 or ...
- emitted value instead of an instance of error the scope attribute for scoped slots webpack babel polyfill
api20180803.vue emitted value instead of an instance of error the scope attribute for scoped slots h ...
- 解决SpringBoot的@Autowired无法注入问题
问题:@Autowired无法自动注入 思路:SpringBoot项目的Bean装配默认规则是根据Application类所在的包位置从上往下扫描!"Application类"是指 ...
- id生成器,分布式ID自增算法(Snowflake 算法)
接口: /** * id生成器 */ public interface IdGenerator { String next(); } 实现类: /** * 分布式ID自增算法<br/> * ...
- CentOs中玩docker
1.启动: systemctl start docker.service 2.停止: systemctl stop docker 3.从usts上拉取仓库 编辑文件 vi /etc/docker/da ...