java -- 性能(from thinking in java)

From: http://www.leftworld.net/online/java/index.html

 

   爲進行客觀的分析,最好明確掌握各種運算的執行時間。這樣一來,得到的結果可獨立於當前使用的計算機——通過除以花在本地賦值上的時間,最後得到的就是“標準時間”。

 

運算

示例

標準時間

本地賦值

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


    提升性能的措施:

    ■字串的開銷:字串連接運算符+看似簡單,但實際需要消耗大量系統資源。編譯器可高效地連接字串,但變量字串卻要求可觀的處理器時間。例如,假設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個單位的賦值時間。開銷更大的甚至要求上溯繼承(遺傳)結構。其他高代價的操作會損失和恢復更低層結構的能力。
■圖形:利用剪切技術,減少在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-in-time”(JIT)編譯器會動態加速代碼。
■儘可能地將計數減至0——這使用了一個特殊的JVM字節碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章