天天看点

Java 优化 代码优化 介绍网站

Think in java 后面的附录章节 《性能》

D . 1 基本方法

只有正确和完整地检测了程序后,再可着手解决性能方面的问题:

(1) 在现实环境中检测程序的性能。若符合要求,则目标达到。若不符合,则转到下一步。

(2) 寻找最致命的性能瓶颈。这也许要求一定的技巧,但所有努力都不会白费。如简单地猜测瓶颈所在,并

试图进行优化,那么可能是白花时间。

(3) 运用本附录介绍的提速技术,然后返回步骤1。

为使努力不至白费,瓶颈的定位是至关重要的一环。Donald Knuth[9]曾改进过一个程序,那个程序把50%

的时间都花在约4%的代码量上。在仅一个工作小时里,他修改了几行代码,使程序的执行速度倍增。此

时,若将时间继续投入到剩余代码的修改上,那么只会得不偿失。Knuth 在编程界有一句名言:“过早的优

化是一切麻烦的根源”(Premature optimization is the root of all evil)。最明智的做法是抑制过早

优化的冲动,因为那样做可能遗漏多种有用的编程技术,造成代码更难理解和操控,并需更大的精力进行维

护。

D . 2 . 4 性能评测的技巧

由于评测时要用到系统时钟,所以当时不要运行其他任何进程或应用程序,以免影响测试结果。

如对自己的程序进行了修改,并试图(至少在开发平台上)改善它的性能,那么在修改前后应分别测

试一下代码的执行时间。

尽量在完全一致的环境中进行每一次时间测试。

如果可能,应设计一个不依赖任何用户输入的测试,避免用户的不同反应导致结果出现误差。

为进行客观的分析,最好明确掌握各种运算的执行时间。这样一来,得到的结果可独立于当前使用的计算

机——通过除以花在本地赋值上的时间,最后得到的就是“标准时间”。

运算 示例 标准时间

本地赋值 i=n; 1.0

实例赋值 this.i=n; 1.2

int 增值 i++; 1.5

byte 增值 b++; 2.0

short 增值 s++; 2.0

float 增值 f++; 2.0

double 增值 d++; 2.0

空循环 while(true) n++; 2.0

三元表达式 (x<0) ?-x : x 2.2

算术调用 Math.abs(x); 2.5

数组赋值 a[0] = n; 2.7

long 增值 l++; 3.5

方法调用 funct(); 5.9

throw 或catch 异常 try{ throw e; }或catch(e){} 320

同步方法调用 synchMehod(); 570

新建对象 new Object(); 980

新建数组 new int[10]; 3100

1. 常规修改

下面是加快Java 程序关键部分执行速度的一些常规操作建议(注意对比修改前后的测试结果)。

将... 修改成... 理由

接口 抽象类(只需一个父时) 接口的多个继承会妨碍性能的优化

非本地或数组循环变量 本地循环变量 根据前表的耗时比较,一次实例整数赋值的时间是本地整数赋值时间

的1.2 倍,但数组赋值的时间是本地整数赋值的2.7 倍

链接列表(固定尺寸) 保存丢弃的链接项目,或将列表替换成一个循环数组(大致知道尺寸) 每新建一个

对象,都相当于本地赋值980 次。

D . 3 . 3 特殊情况

■字串的开销:字串连接运算符+看似简单,但实际需要消耗大量系统资源。编译器可高效地连接字串,但变

量字串却要求可观的处理器时间。例如,假设s 和t 是字串变量:

System.out.println("heading" + s + "trailer" + t);

上述语句要求新建一个StringBuffer(字串缓冲),追加自变量,然后用toString()将结果转换回一个字

串。因此,无论磁盘空间还是处理器时间,都会受到严重消耗。若准备追加多个字串,则可考虑直接使用一

个字串缓冲——特别是能在一个循环里重复利用它的时候。通过在每次循环里禁止新建一个字串缓冲,可节

省980 单位的对象创建时间(如前所述)。利用substring()以及其他字串方法,可进一步地改善性能。如

果可行,字符数组的速度甚至能够更快。也要注意由于同步的关系,所以StringTokenizer 会造成较大的开

销。

■同步:在JDK 解释器中,调用同步方法通常会比调用不同步方法慢10 倍。经JIT 编译器处理后,这一性能

上的差距提升到50 到100 倍(注意前表总结的时间显示出要慢97 倍)。所以要尽可能避免使用同步方法—

—若不能避免,方法的同步也要比代码块的同步稍快一些。

■重复利用对象:要花很长的时间来新建一个对象(根据前表总结的时间,对象的新建时间是赋值时间的

980 倍,而新建一个小数组的时间是赋值时间的3100 倍)。因此,最明智的做法是保存和更新老对象的字

段,而不是创建一个新对象。例如,不要在自己的paint()方法中新建一个Font 对象。相反,应将其声明成

实例对象,再初始化一次。在这以后,可在paint()里需要的时候随时进行更新。参见Bentley 编著的《编

程拾贝》,p.81[15]。

■异常:只有在不正常的情况下,才应放弃异常处理模块。什么才叫“不正常”呢?这通常是指程序遇到了

问题,而这一般是不愿见到的,所以性能不再成为优先考虑的目标。进行优化时,将小的“try-catch”块合

并到一起。由于这些块将代码分割成小的、各自独立的片断,所以会妨碍编译器进行优化。另一方面,若过

份热衷于删除异常处理模块,也可能造成代码健壮程度的下降。

■散列处理:首先,Java 1.0 和1.1 的标准“散列表”(Hashtable)类需要造型以及特别消耗系统资源的

同步处理(570 单位的赋值时间)。其次,早期的JDK 库不能自动决定最佳的表格尺寸。最后,散列函数应

针对实际使用项(Key)的特征设计。考虑到所有这些原因,我们可特别设计一个散列类,令其与特定的应用

程序配合,从而改善常规散列表的性能。注意Java 1.2 集合库的散列映射(HashMap)具有更大的灵活性,

而且不会自动同步。

■方法内嵌:只有在方法属于final(最终)、private(专用)或static(静态)的情况下,Java 编译器

才能内嵌这个方法。而且某些情况下,还要求它绝对不可以有局部变量。若代码花大量时间调用一个不含上

述任何属性的方法,那么请考虑为其编写一个“final”版本。

■I/O:应尽可能使用缓冲。否则,最终也许就是一次仅输入/输出一个字节的恶果。注意JDK 1.0 的I/O 类

采用了大量同步措施,所以若使用象readFully()这样的一个“大批量”调用,然后由自己解释数据,就可

获得更佳的性能。也要注意Java 1.1 的“reader”和“writer”类已针对性能进行了优化。

■造型和实例:造型会耗去2 到200 个单位的赋值时间。开销更大的甚至要求上溯继承(遗传)结构。其他

■使用API 类:尽量使用来自Java API 的类,因为它们本身已针对机器的性能进行了优化。这是用Java 难

于达到的。比如在复制任意长度的一个数组时,arraryCopy()比使用循环的速度快得多。

■替换API 类:有些时候,API 类提供了比我们希望更多的功能,相应的执行时间也会增加。因此,可定做

特别的版本,让它做更少的事情,但可更快地运行。例如,假定一个应用程序需要一个容器来保存大量数

组。为加快执行速度,可将原来的Vector(矢量)替换成更快的动态对象数组。

1. 其他建议

■将重复的常数计算移至关键循环之外——比如计算固定长度缓冲区的buffer.length。

■static final(静态最终)常数有助于编译器优化程序。

■实现固定长度的循环。

■使用javac 的优化选项:-O。它通过内嵌static,final 以及private 方法,从而优化编译过的代码。注

意类的长度可能会增加(只对JDK 1.1 而言——更早的版本也许不能执行字节查证)。新型的“Just-intime”(

JIT)编译器会动态加速代码。

D . 4 参考资源

D . 4 . 1 性能工具

[1] 运行于Pentium Pro 200,Netscape 3.0,JDK 1.1.4 的MicroBenchmark(参见下面的参考资源[5])

[2] Sun 的Java 文档页——JDK Java 解释器主题:

http://java.sun.com/products/JDK/tools/win32/java.html

[3] Vladimir Bulatov 的HyperProf

http://www.physics.orst.edu/~bulatov/HyperProf

[4] Greg White 的ProfileViewer

http://www.inetmi.com/~gwhi/ProfileViewer/ProfileViewer.html

D . 4 . 2 W e b 站点

[5] 对于Java 代码的优化主题,最出色的在线参考资源是Jonathan Hardwick 的“Java Optimization”网

站:

http://www.cs.cmu.edu/~jch/java/optimization.html

“Java 优化工具”主页:

http://www.cs.cmu.edu/~jch/java/tools.html

以及“Java Microbenchmarks”(有一个45 秒钟的评测过程):

http://www.cs.cmu.edu/~jch/java/benchmarks.html

D . 4 . 3 文章

[6] “Make Java fast:Optimize! How to get the greatest performanceout of your code through lowlevel

optimizations in Java”(让Java 更快:优化!如何通过在Java 中的低级优化,使代码发挥最出色

的性能)。作者:Doug Bell。网址:

http://www.javaworld.com/javaworld/jw-04-1997/jw-04-optimize.html

(含一个全面的性能评测程序片,有详尽注释)

[7] “Java Optimization Resources ”(Java 优化资源)

http://www.cs.cmu.edu/~jch/java/resources.html

[8] “Optimizing Java for Speed”(优化Java,提高速度):

http://www.cs.cmu.edu/~jch/java/speed.html