📄 appd.htm
字号:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>Thinking in Java | Chinese Version by Trans Bot</title>
<meta name="Microsoft Theme" content="inmotion 111, default"></head>
<body background="../_themes/inmotion/inmtextb.gif" tppabs="http://member.netease.com/%7etransbot/Thinking%20in%20Java/_themes/inmotion/inmtextb.gif" bgcolor="#FFFFCC" text="#000000" link="#800000" vlink="#996633" alink="#FF3399">
<p>附录D 性能<br>
<br>
“本附录由Joe Sharp投稿,并获得他的同意在这儿转载。请联系SharpJoe@aol.com”<br>
<br>
Java语言特别强调准确性,但可靠的行为要以性能作为代价。这一特点反映在自动收集垃圾、严格的运行期检查、完整的字节码检查以及保守的运行期同步等等方面。对一个解释型的虚拟机来说,由于目前有大量平台可供挑选,所以进一步阻碍了性能的发挥。<br>
“先做完它,再逐步完善。幸好需要改进的地方通常不会太多。”(Steve
McConnell的《About performance》[16])<br>
本附录的宗旨就是指导大家寻找和优化“需要完善的那一部分”。<br>
<br>
D.1 基本方法<br>
只有正确和完整地检测了程序后,再可着手解决性能方面的问题:<br>
(1)
在现实环境中检测程序的性能。若符合要求,则目标达到。若不符合,则转到下一步。<br>
(2)
寻找最致命的性能瓶颈。这也许要求一定的技巧,但所有努力都不会白费。如简单地猜测瓶颈所在,并试图进行优化,那么可能是白花时间。<br>
(3) 运用本附录介绍的提速技术,然后返回步骤1。<br>
<br>
为使努力不至白费,瓶颈的定位是至关重要的一环。Donald Knuth[9]曾改进过一个程序,那个程序把50%的时间都花在约4%的代码量上。在仅一个工作小时里,他修改了几行代码,使程序的执行速度倍增。此时,若将时间继续投入到剩余代码的修改上,那么只会得不偿失。Knuth在编程界有一句名言:“过早的优化是一切麻烦的根源”(Premature
optimization is the root of all evil)。最明智的做法是抑制过早优化的冲动,因为那样做可能遗漏多种有用的编程技术,造成代码更难理解和操控,并需更大的精力进行维护。<br>
<br>
D.2 寻找瓶颈<br>
为找出最影响程序性能的瓶颈,可采取下述几种方法:<br>
<br>
D.2.1 安插自己的测试代码<br>
插入下述“显式”计时代码,对程序进行评测:<br>
<br>
long start = System.currentTimeMillis();<br>
// 要计时的运算代码放在这儿<br>
long time = System.currentTimeMillis() - start;<br>
<br>
利用System.out.println(),让一种不常用到的方法将累积时间打印到控制台窗口。由于一旦出错,编译器会将其忽略,所以可用一个“静态最终布尔值”(Static
final boolean)打开或关闭计时,使代码能放心留在最终发行的程序里,这样任何时候都可以拿来应急。尽管还可以选用更复杂的评测手段,但若仅仅为了量度一个特定任务的执行时间,这无疑是最简便的方法。<br>
System.currentTimeMillis()返回的时间以千分之一秒(1毫秒)为单位。然而,有些系统的时间精度低于1毫秒(如Windows
PC),所以需要重复n次,再将总时间除以n,获得准确的时间。<br>
<br>
D.2.2 JDK性能评测[2]<br>
JDK配套提供了一个内建的评测程序,能跟踪花在每个例程上的时间,并将评测结果写入一个文件。不幸的是,JDK评测器并不稳定。它在JDK
1.1.1中能正常工作,但在后续版本中却非常不稳定。<br>
为运行评测程序,请在调用Java解释器的未优化版本时加上-prof选项。例如:<br>
java_g -prof myClass<br>
或加上一个程序片(Applet):<br>
java_g -prof sun.applet.AppletViewer applet.html<br>
理解评测程序的输出信息并不容易。事实上,在JDK 1.0中,它居然将方法名称截短为30字符。所以可能无法区分出某些方法。然而,若您用的平台确实能支持-prof选项,那么可试试Vladimir
Bulatov的“HyperPorf”[3]或者Greg White的“ProfileViewer”来解释一下结果。<br>
<br>
D.2.3 特殊工具<br>
如果想随时跟上性能优化工具的潮流,最好的方法就是作一些Web站点的常客。比如由Jonathan
Hardwick制作的“Tools for Optimizing Java”(Java优化工具)网站:<br>
http://www.cs.cmu.edu/~jch/java/tools.html<br>
<br>
D.2.4 性能评测的技巧<br>
■由于评测时要用到系统时钟,所以当时不要运行其他任何进程或应用程序,以免影响测试结果。<br>
■如对自己的程序进行了修改,并试图(至少在开发平台上)改善它的性能,那么在修改前后应分别测试一下代码的执行时间。<br>
■尽量在完全一致的环境中进行每一次时间测试。<br>
■如果可能,应设计一个不依赖任何用户输入的测试,避免用户的不同反应导致结果出现误差。<br>
<br>
D.3 提速方法<br>
现在,关键的性能瓶颈应已隔离出来。接下来,可对其应用两种类型的优化:常规手段以及依赖Java语言。<br>
<br>
D.3.1 常规手段<br>
通常,一个有效的提速方法是用更现实的方式重新定义程序。例如,在《Programming
Pearls》(编程拾贝)一书中[14],Bentley利用了一段小说数据描写,它可以生成速度非常快、而且非常精简的拼写检查器,从而介绍了Doug
McIlroy对英语语言的表述。除此以外,与其他方法相比,更好的算法也许能带来更大的性能提升——特别是在数据集的尺寸越来越大的时候。欲了解这些常规手段的详情,请参考本附录末尾的“一般书籍”清单。<br>
<br>
D.3.2 依赖语言的方法<br>
为进行客观的分析,最好明确掌握各种运算的执行时间。这样一来,得到的结果可独立于当前使用的计算机——通过除以花在本地赋值上的时间,最后得到的就是“标准时间”。<br>
<br>
运算 示例 标准时间<br>
<br>
本地赋值 i=n; 1.0<br>
实例赋值 this.i=n; 1.2<br>
int增值 i++; 1.5<br>
byte增值 b++; 2.0<br>
short增值 s++; 2.0<br>
float增值 f++; 2.0<br>
double增值 d++; 2.0<br>
空循环 while(true) n++; 2.0<br>
三元表达式 (x<0) ?-x : x 2.2<br>
算术调用 Math.abs(x); 2.5<br>
数组赋值 a[0] = n; 2.7<br>
long增值 l++; 3.5<br>
方法调用 funct(); 5.9<br>
throw或catch异常 try{ throw e; }或catch(e){} 320<br>
同步方法调用 synchMehod(); 570<br>
新建对象 new Object(); 980<br>
新建数组 new int[10]; 3100<br>
<br>
通过自己的系统(如我的Pentium 200 Pro,Netscape 3及JDK 1.1.5),这些相对时间向大家揭示出:新建对象和数组会造成最沉重的开销,同步会造成比较沉重的开销,而一次不同步的方法调用会造成适度的开销。参考资源[5]和[6]为大家总结了测量用程序片的Web地址,可到自己的机器上运行它们。<br>
<br>
1. 常规修改<br>
下面是加快Java程序关键部分执行速度的一些常规操作建议(注意对比修改前后的测试结果)。<br>
<br>
将... 修改成... 理由<br>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -