83.JAVA编程思想——关于JAVA性能

83.JAVA编程思想——关于JAVA性能

Java 语言特别强调准确性,但可靠的行为要以性能作为代价。这一特点反映在自动收集垃圾、严格的运行期检查、完整的字节码检查以及保守的运行期同步等等方面。对一个解释型的虚拟机来说,由于目前有大量平台可供挑选,所以进一步阻碍了性能的发挥。

“先做完它,再逐步完善。幸好需要改进的地方通常不会太多。”

1     基本方法

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

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

(2) 寻找最致命的性能瓶颈。这也许要求一定的技巧,但所有努力都不会白费。如简单地猜测瓶颈所在,并试图进行优化,那么可能是白花时间。

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

为使努力不至白费,瓶颈的定位是至关重要的一环。DonaldKnuth[9]曾改进过一个程序,那个程序把50%的时间都花在约4%的代码量上。在仅一个工作小时里,他修改了几行代码,使程序的执行速度倍增。此时,若将时间继续投入到剩余代码的修改上,那么只会得不偿失。Knuth 在编程界有一句名言:“过早的优化是一切麻烦的根源”(Prematureoptimization is the root of all evil)。最明智的做法是抑制过早优化的冲动,因为那样做可能遗漏多种有用的编程技术,造成代码更难理解和操控,并需更大的精力进行维护。

2     寻找瓶颈

为找出最影响程序性能的瓶颈,可采取下述几种方法:

2.1     安插自己的测试代码

插入下述“显式”计时代码,对程序进行评测:

long start = System.currentTimeMillis();

// 要计时的运算代码放在这儿

long time =System.currentTimeMillis() - start;

利用System.out.println(),让一种不常用到的方法将累积时间打印到控制台窗口。由于一旦出错,编译器会将其忽略,所以可用一个“静态最终布尔值”(Static final boolean)打开或关闭计时,使代码能放心留在最终发行的程序里,这样任何时候都可以拿来应急。尽管还可以选用更复杂的评测手段,但若仅仅为了量度一个特定任务的执行时间,这无疑是最简便的方法。

System.currentTimeMillis()返回的时间以千分之一秒(1 毫秒)为单位。然而,有些系统的时间精度低于1毫秒(如Windows PC),所以需要重复n 次,再将总时间除以n,获得准确的时间。

2.2     J D K 性能评测

JDK 配套提供了一个内建的评测程序,能跟踪花在每个例程上的时间,并将评测结果写入一个文件。不幸的是,JDK 评测器并不稳定。它在JDK 1.1.1 中能正常工作,但在后续版本中却非常不稳定。为运行评测程序,请在调用Java 解释器的未优化版本时加上-prof 选项。例如:

java_g -prof myClass

或加上一个程序片(Applet):

680

java_g -profsun.applet.AppletViewer applet.html

理解评测程序的输出信息并不容易。事实上,在JDK1.0 中,它居然将方法名称截短为30 字符。所以可能无法区分出某些方法。然而,若您用的平台确实能支持-prof 选项,那么可试试Vladimir Bulatov 的“HyperPorf”[3]或者GregWhite 的“ProfileViewer”来解释一下结果。

3     特殊工具

如果想随时跟上性能优化工具的潮流,最好的方法就是作一些Web 站点的常客。比如由Jonathan Hardwick制作的“Tools for Optimizing Java”(Java 优化工具)网站:

4     性能评测的技巧

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

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

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

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

5     提速方法

现在,关键的性能瓶颈应已隔离出来。接下来,可对其应用两种类型的优化:常规手段以及依赖Java 语言。

6     常规手段

通常,一个有效的提速方法是用更现实的方式重新定义程序。例如,在《Programming Pearls》(编程拾贝)一书中[14],Bentley 利用了一段小说数据描写,它可以生成速度非常快、而且非常精简的拼写检查器,从而介绍了Doug McIlroy 对英语语言的表述。除此以外,与其他方法相比,更好的算法也许能带来更大的性能提升——特别是在数据集的尺寸越来越大的时候。

7     依赖语言的方法

为进行客观的分析,最好明确掌握各种运算的执行时间。这样一来,得到的结果可独立于当前使用的计算机——通过除以花在本地赋值上的时间,最后得到的就是“标准时间”。

运算 示例 标准时间

本地赋值 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 : x2.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

新建对象和数组会造成最沉重的开销,同步会造成比较沉重的开销,而一次不同步的方法调用会造成适度的开销。

8     特殊情况

■字串的开销:字串连接运算符+看似简单,但实际需要消耗大量系统资源。编译器可高效地连接字串,但变量字串却要求可观的处理器时间。例如,假设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 编著的《编程拾贝》

■异常:只有在不正常的情况下,才应放弃异常处理模块。什么才叫“不正常”呢?这通常是指程序遇到了问题,而这一般是不愿见到的,所以性能不再成为优先考虑的目标。进行优化时,将小的“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 个单位的赋值时间。开销更大的甚至要求上溯继承(遗传)结构。其他高代价的操作会损失和恢复更低层结构的能力。

■图形:利用剪切技术,减少在repaint()中的工作量;倍增缓冲区,提高接收速度;同时利用图形压缩技术,缩短下载时间。来自JavaWorld 的“Java Applets”以及来自Sun 的“Performing Animation”是两个很好的教程。请记着使用最贴切的命令。例如,为根据一系列点画一个多边形,和drawLine()相比,drawPolygon()的速度要快得多。如必须画一条单像素粗细的直线,drawLine(x,y,x,y)的速度比fillRect(x,y,1,1)快。

■使用API 类:尽量使用来自Java API 的类,因为它们本身已针对机器的性能进行了优化。这是用Java 难于达到的。比如在复制任意长度的一个数组时,arraryCopy()比使用循环的速度快得多。

■替换API 类:有些时候,API 类提供了比我们希望更多的功能,相应的执行时间也会增加。因此,可定做特别的版本,让它做更少的事情,但可更快地运行。例如,假定一个应用程序需要一个容器来保存大量数组。为加快执行速度,可将原来的Vector(矢量)替换成更快的动态对象数组。

1. 其他建议

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

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

■实现固定长度的循环。

■使用javac 的优化选项:-O。它通过内嵌static,final以及private 方法,从而优化编译过的代码。注意类的长度可能会增加(只对JDK 1.1 而言——更早的版本也许不能执行字节查证)。新型的“Just-intime”(JIT)编译器会动态加速代码。

■尽可能地将计数减至0——这使用了一个特殊的JVM 字节码。

时间: 2024-09-30 00:27:26

83.JAVA编程思想——关于JAVA性能的相关文章

Java编程思想重点笔记(Java开发必看)

Java编程思想,Java学习必读经典,不管是初学者还是大牛都值得一读,这里总结书中的重点知识,这些知识不仅经常出现在各大知名公司的笔试面 试过程中,而且在大型项目开发中也是常用的知识,既有简单的概念理解题(比如is-a关系和has-a关系的区别),也有深入的涉及RTTI和JVM底层 反编译知识. 1. Java中的多态性理解(注意与C++区分) Java中除了static方法和final方法(private方法本质上属于final方法,因为不能被子类访问)之外,其它所有的方法都是动态绑定,这意

71.JAVA编程思想——JAVA与CGI

71.JAVA编程思想--JAVA与CGI Java 程序可向一个服务器发出一个CGI 请求,这与HTML 表单页没什么两样.而且和HTML 页一样,这个请求既可以设为GET(下载),亦可设为POST(上传).除此以外,Java 程序还可拦截CGI 程序的输出,所以不必依赖程序来格式化一个新页,也不必在出错的时候强迫用户从一个页回转到另一个页.事实上,程序的外观可以做得跟以前的版本别无二致. 代码也要简单一些,毕竟用CGI 也不是很难就能写出来(前提是真正地理解它).所以我们准备办个CGI 编程

java编程思想总结(三)

java编程思想总结(三) java编程思想总结是一个持续更新的系列,是本人对自己多年工作中使用到java的一个经验性总结,也是温故而知新吧,因为很多基础的东西过了这么多年,平时工作中用不到也会遗忘掉,所以看看书,上上网,查查资料,也算是记录下自己的笔记吧,过一段时间之后再来看看也是蛮不错的,也希望能帮助到正在学习的人们,本系列将要总结一下几点: 面向对象的编程思想 java的基本语法 一些有趣的框架解析 实战项目的整体思路 代码的优化以及性能调优的几种方案 整体项目的规划和视角 其它遗漏的东西

65.JAVA编程思想——关于Runnable

65.JAVA编程思想--关于Runnable 在早些时候,曾建议大家在将一个程序片或主Frame 当作Runnable 的实现形式之前,一定要好好地想一想.若采用那种方式,就只能在自己的程序中使用其中的一个线程.这便限制了灵活性,一旦需要用到属于那种类型的多个线程,就会遇到不必要的麻烦. 当然,如果必须从一个类继承,而且想使类具有线程处理能力,则Runnable 是一种正确的方案.最后一个例子对这一点进行了剖析,制作了一个RunnableCanvas类,用于为自己描绘不同的颜色(Canvas

70.JAVA编程思想——Web应用

70.JAVA编程思想--Web应用 创建一个应用,令其在真实的Web 环境中运行,它将把Java 的优势表现得淋漓尽致.这个应用的一部分是在Web 服务器上运行的一个Java 程序,另一部分则是一个"程序片"或"小应用程序"(Applet),从服务器下载至浏览器(即"客户").这个程序片从用户那里收集信息,并将其传回Web 服务器上运行的应用程序.程序的任务非常简单:程序片会询问用户的E-mail 地址,并在验证这个地址合格后(没有包含空格,而

java编程思想学习(1)

Smalltalk 这是第一种成功的面向对象程序设计语言,也是Java 的基础语言.Smalltalk (java的基础语言)的五大基本特征: (1) 所有东西都是对象.可将对象想象成一种新型变量:它保存着数据,但可要求它对自身进行操作.理论上讲,可从要解决的问题身上提出所有概念性的组件,然后在程序中将其表达为一个对象. (2) 程序是一大堆对象的组合:通过消息传递,各对象知道自己该做些什么.为了向对象发出请求,需向那个对象"发送一条消息".更具体地讲,可将消息想象为一个调用请求,它调

java编程思想总结(二)

java编程思想总结(二) java编程思想总结是一个持续更新的系列,是本人对自己多年工作中使用到java的一个经验性总结,也是温故而知新吧,因为很多基础的东西过了这么多年,平时工作中用不到也会遗忘掉,所以看看书,上上网,查查资料,也算是记录下自己的笔记吧,过一段时间之后再来看看也是蛮不错的,也希望能帮助到正在学习的人们,本系列将要总结一下几点: 面向对象的编程思想 java的基本语法 一些有趣的框架解析 实战项目的整体思路 代码的优化以及性能调优的几种方案 整体项目的规划和视角 其它遗漏的东西

12.JAVA编程思想——集合的类型

12.JAVA编程思想--集合的类型 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/51100510 标准Java 1.0 和1.1 库配套提供了非常少的一系列集合类.但对于自己的大多数编程要求,它们基本上都能胜任.Java 1.2 提供的是一套重新设计过的大型集合库. 1      Vector Vector 的用法很简单,大多数时候只需用addElement()插入对象,用elementAt()一次提取一个对象,并用el

77.JAVA编程思想——模拟垃圾回收

77.JAVA编程思想--模拟垃圾回收 这个问题的本质是若将垃圾丢进单个垃圾筒,事实上是未经分类的.但在以后,某些特殊的信息必须恢复,以便对垃圾正确地归类.在最开始的解决方案中,RTTI 扮演了关键的角色.这并不是一种普通的设计,因为它增加了一个新的限制.正是这个限制使问题变得非常有趣--它更象我们在工作中碰到的那些非常麻烦的问题.这个额外的限制是:垃圾抵达垃圾回收站时,它们全都是混合在一起的.程序必须为那些垃圾的分类定出一个模型.这正是RTTI 发挥作用的地方:我们有大量不知名的垃圾,程序将正