標籤:
效能
一般來說,效能通過以下幾個方面來表現:
定量評測的效能指標:
- 執行時間
- CPU時間
- 記憶體配置
- 磁碟輸送量
- 網路輸送量
- 回應時間
調優的層面
- 設計調優
- 代碼調優
- JVM調優
- 資料庫調優
- 作業系統調優
效能調優必須有明確的目標,不要為了調優而調優,如果當前程式並沒有明顯的效能問題,盲目地進行調整,其風險可能遠遠大於收益。
設計最佳化
1. 單例模式
對於系統的關鍵組件和被頻繁使用的對象,使用單例模式可以有效地改善系統的效能
2. 代理模式
代理模式可以用來實現消極式載入,從而提升系統的效能和反應速度。
另外,可以考慮使用動態代理的方式 。 動態代理的方法有: JDK內建的動態代理, CGLIB, Javassist, 或ASM庫。
3. 享元模式
好處:
1) 可以節省重複建立對象的開銷
2) 對系統記憶體的需求減少
4. 裝飾者模式
實現效能組件與功能組件的完美分離
5. 觀察者模式
觀察者模式可以用於事件監聽、通知發布等場合。可以確保觀察者在不使用輪詢監控的情況下,及時收到相關的訊息和事件。
6. Value Object 模式
將一個對象的各個屬性進行封裝,將封裝後的對象在網路中傳遞,從而使系統擁有更好的互動模型,並且減少網路通訊資料,從而提高系統效能。
7. 業務代理模式
將一組由遠程方法調用構成的商務程序,封裝在一個位於展示層的代理類中。
思考:
單例模式, 原廠模式和享元模式的差異?
常用最佳化組件和方法
1.緩衝
I/O 操作很容易成為效能瓶頸,所以,儘可能在 I/O 讀寫中加入緩衝組件,以提高系統的效能。
2. 緩衝
緩衝可以儲存一些來之不易的資料或者計算結果,當需要再次使用這些資料時,可以從緩衝中低成本地擷取,而不需要再佔用寶貴的系統資源。
Java緩衝架構:
EHCache, OSCache,JBossCache
3. 對象複用 -- "池"
最熟悉的線程池和資料庫連接池。
目前應用較為廣泛的資料庫連接池組件有C3P0 和Proxool.
4.並行替代串列
5. 負載平衡
跨JVM虛擬機器,專門用於分布式緩衝的架構--Terracotta, 使用Terracotta可以實現Tomcat的Session共用。
6. 時間換空間
7. 空間換時間
程式最佳化
1. 字串最佳化處理
1)
String str1 ="abc";String str2 ="abc";String str3 = new String("abc");System.out.println(str1==str2); //trueSystem.out.println(str1==str3); //falseSystem.out.println(str1==str3.intern()); //true2) subString() 方法的記憶體流失
如果原字串很長,截取的字串卻有比較短,使用以下方式返回:
new String(str1.substring(begin,end));
3) 字串分割和尋找
可以使用的方法:
split()方法 -- 最慢, 寫法簡單
StringTokenizer 方法 -- 較快,寫法一般
indexOf()和subString() 方法 - 最快, 寫法麻煩
package performance.program.string;import java.util.StringTokenizer;public class StringSplit {public static void splitMethod(String str) {long beginTime = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {str.split(";");}long endTime = System.currentTimeMillis();System.out.println("splitMethod use " + (endTime - beginTime));}public static void tokenizerMethod(String str) {long beginTime = System.currentTimeMillis();StringTokenizer st = new StringTokenizer(str, ";");for (int i = 0; i < 10000; i++) {while (st.hasMoreTokens()) {st.nextToken();}st = new StringTokenizer(str, ";");}long endTime = System.currentTimeMillis();System.out.println("tokenizerMethod use " + (endTime - beginTime));}public static void IndexMethod(String str) {long beginTime = System.currentTimeMillis();String tmp = str;for (int i = 0; i < 10000; i++) {while (true) {String splitStr = null;int j = tmp.indexOf(";");if(j<0) break;splitStr = tmp.substring(0,j);tmp = tmp.substring(j+1);}tmp = str;}long endTime = System.currentTimeMillis();System.out.println("IndexMethod use " + (endTime - beginTime));}/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubString orgStr = null;StringBuffer sb = new StringBuffer();for (int i = 0; i < 1000; i++) {sb.append(i);sb.append(";");}orgStr = sb.toString();splitMethod(orgStr);tokenizerMethod(orgStr);IndexMethod(orgStr);}}
4) 使用ChartAt 代替 startsWith 和 endsWith
效能要求比較高時,可以使用這條。
5) StringBuffer 和 StringBuilder
String result = "String" + "and" + "string"+"append";
這段看起來效能不高的代碼在實際執行時反而會比StringBuilder 來的快。
原因是Java 編譯器本身會做最佳化。
但是不能完全依靠編譯器的最佳化, 還是建議顯示地使用StringBuffer 或StringBuffer對象來提升系統效能。
StringBuffer 和 StringBuilder 的差異是:
StingBuffer 幾乎所有的方法都做了同步
StringBuilder 並沒有任何同步。
所以StringBuilder 的效率好於StringBuffer, 但是在多線程系統中,StringBuilder 無法保證安全執行緒。
另外,預先評估StringBuilder 的大小,能提升系統的效能。
2. 核心資料結構
1) List 介面
3種List實現: ArrayList, Vector, 和LinkedList
ArrayList 和Vector 使用了數組實現, Vector 絕大部分方法都做了線程同步, ArrayList 沒有對任何一個方法做線程同步。(ArrayList 和Vector 看上去效能相差無幾)
使用LinkedList 對堆記憶體和GC的要求更高。
如果在系統應用中, List對象需要經常在任意位置插入元素,則可以考慮使用LinkedList 替代ArrayList
對於ArrayList從尾部刪除元素時效率很高,從頭部刪除元素時相當費時,
而LinkedList 從頭尾刪除元素時效率相差無幾,但是從中間刪除元素時效能非常槽糕。
| |
頭部 |
中間 |
尾部 |
| ArrayList |
6203 |
3125 |
16 |
| LinkedList |
15 |
8781 |
16 |
List 遍曆操作
| |
ForEach |
迭代器 |
for 迴圈 |
| ArrayList |
63 |
47 |
31 |
| LinkedList |
63 |
47 |
無窮大 |
2) Map 介面
實作類別有: Hashtable, HashMap, LinkedHashMap和TreeMap
HashTable 和HashMap 的差異
HashTable大部分方法做了同步, HashTable 不允許key或者Value 使用null值;(效能上看起來無明顯差異)
3) Set 介面
4) 最佳化集合存取碼
1. 分離迴圈中被重複調用的代碼
for(int i=0;i<collection.size();i++)
替換成
int count = collection.size();
for(int i=0;i<count;i++)
5). RandomAccess介面
3. 使用NIO 提升效能
在Java 的標準I/O中, 提供了基於流的I/O 實現, 即InputStream 和 OutputStream. 這種基於流的實現是以位元組為單位處理資料, 並且非常容易建立各種過濾器。
NIO是 New I/O 的簡稱, 與舊式的基於流的 I/O 方法相對, 它表示新的一套Java I/O 標準。是在Java 1.4 中被納入到JDK中的, 特性有
- 為所有的原始類型提供 Buffer 緩衝支援
- 使用Java.nio.charset.Charset 作為字元集編碼解碼解決方案
- 增加通道對象(Channel), 作為新的原始I/O 抽象
- 支援鎖和記憶體對應檔的檔案提供者
- 提供了基於Selector 的非同步網路I/O
與流式的I/O 不同, NIO 是基於塊(Block 的), 它以塊為基本單位處理資料。
4. 參考型別。
強引用、軟引用、弱引用和虛引用
強引用:
a)強引用可以直接存取目標對象
b) 強引用所指向的對象在任何時候都不會被系統回收
c)強引用可能導致記憶體流失
軟引用:
軟引用可以通過java.lang.ref.SoftReference來使用。 一個持有軟引用的對象,不會被JVM很快回收, JVM會根據當前的使用狀況來判斷何時回收.當堆使用率臨近閾值時,才會去回收軟引用的對象。只要有足夠的記憶體,軟引用便可能在記憶體中存活相對長一段時間。因此,軟引用可以用於實現對記憶體敏感的Cache.
弱引用:
在系統GC時,只要發現弱引用,不管系統堆空間是否足夠,都會將對象進行回收。
虛引用
一個持有虛引用的對象,和沒有引用幾乎是一樣的,隨時都可能被記憶體回收行程回收。但試圖通過虛引用的get()方法去跌強引用時,總是會失敗。並且,虛引用必須和引用隊列一起使用,它的作用在於跟蹤回收過程。
WeakHashMap類及其實現
WeakHashMap 是弱引用的一種典型應用,它可以作為簡單的緩衝表解決方案。
改善系統效能的技巧
1. 慎用異常
try catch 應用與迴圈體之外
2. 使用局部變數
調用方法時傳遞的參數以及在調用中建立的臨時變數都儲存在棧中,速度較快。
其他變數,如靜態變數、執行個體變數等,都在堆中建立,速度較慢。
局部變數的訪問速度遠遠高於類的成員變數。
3. 位元運算代替乘除法
4. 替換Switch .
這一條應該注意的是一個新的思路問題, 使用不同的方式代替switch 語句。
這裡的例子效能測試起來, switch 的效能反而更好一些。
package com.oscar999.performance.skill;public class SwitchReplaceSkill {/** * @param args */protected void oldMethod() {int re = 0;for (int i = 0; i < 100000000; i++) {re = switchInt(i);}}public void newMethod() {int re=0;int[] sw= new int[]{0,3,6,7,8,10,16,18,44};for(int i = 0; i < 100000000; i++) {re = arrayInt(sw,i);}}protected int switchInt(int z) {int i = z % 10 + 1;switch (i) {case 1:return 3;case 2:return 6;case 3:return 7;case 4:return 8;case 5:return 10;case 6:return 16;case 7:return 18;case 8:return 44;default:return -1;}}protected int arrayInt(int[] sw,int z){int i=z%10+1;if(i>8||i<1){return -1;}else{return sw[i];}}public static void main(String[] args) {// TODO Auto-generated method stubSwitchReplaceSkill skill = new SwitchReplaceSkill();long begTime = System.currentTimeMillis();skill.oldMethod();long endTime = System.currentTimeMillis();System.out.println("Old Method use: "+(endTime-begTime));begTime = System.currentTimeMillis();skill.newMethod();endTime = System.currentTimeMillis();System.out.println("New Method use: "+(endTime-begTime));}}
5. 一維數組代替二維數組
直接看例子:
package com.oscar999.performance.skill;public class OneDime2TwoDime {protected void oldMethod(){int[][] array = new int[1000][1000];int re = 0;for(int k=0;k<100;k++) for(int i=0;i<array[0].length;i++) for(int j=0;j<array[0].length;j++) array[i][j] = i;for(int k=0;k<100;k++) for(int i=0;i<array[0].length;i++) for(int j=0;j<array[0].length;j++) re=array[i][j];}protected void newMethod(){int[] array = new int[1000000];int re = 0;for(int k=0;k<100;k++)for(int i=0;i<array.length;i++)array[i] = i;for(int k=0;k<100;k++)for(int i=0;i<array.length;i++)re = array[i];}/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubOneDime2TwoDime skill = new OneDime2TwoDime();long begTime = System.currentTimeMillis();skill.oldMethod();long endTime = System.currentTimeMillis();System.out.println("Old Method use: "+(endTime-begTime));begTime = System.currentTimeMillis();skill.newMethod();endTime = System.currentTimeMillis();System.out.println("New Method use: "+(endTime-begTime));}}Old Method use: 453
New Method use: 281
一維數字用的時間少了很多。
6. 提取運算式
有些重複運算的部分可以提取出來。
package com.oscar999.performance.skill;public class ExtraExpression {protected void oldMethod(){double d = Math.random();double a = Math.random();double b = Math.random();double e = Math.random();double x,y;for(int i=0;i<10000000;i++){x = d*a*b/3*4*a;x = e*a*b/3*4*a;}}protected void newMethod(){double d = Math.random();double a = Math.random();double b = Math.random();double e = Math.random();double x,y,t;for(int i=0;i<10000000;i++){t = a*b/3*4*a;x = d*t;x = e*t;}}/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubOneDime2TwoDime skill = new OneDime2TwoDime();long begTime = System.currentTimeMillis();skill.oldMethod();long endTime = System.currentTimeMillis();System.out.println("Old Method use: "+(endTime-begTime));begTime = System.currentTimeMillis();skill.newMethod();endTime = System.currentTimeMillis();System.out.println("New Method use: "+(endTime-begTime));}}
Old Method use: 109
New Method use: 79
7. 展開迴圈
展開迴圈是一種在極端情況下使用的最佳化手段,因為展開循可能會影響代碼的可讀性和可維護性, 所以取捨之間, 就要根據實際狀況來看了。
看例子:
package com.oscar999.performance.skill;public class ExpandCycle {protected void oldMethod(){int[] array = new int[9999999];for(int i=0;i<9999999;i++){array[i]=i;}}protected void newMethod(){int[] array = new int[9999999];for(int i=0;i<9999999;i+=3){array[i]=i;array[i+1]=i+1;array[i+2]=i+2;}}/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubExpandCycle skill = new ExpandCycle();long begTime = System.currentTimeMillis();skill.oldMethod();long endTime = System.currentTimeMillis();System.out.println("Old Method use: "+(endTime-begTime));begTime = System.currentTimeMillis();skill.newMethod();endTime = System.currentTimeMillis();System.out.println("New Method use: "+(endTime-begTime));}}Old Method use: 78
New Method use: 47
8. 布爾運算代替位元運算
雖然位元運算的速度遠遠高於算術運算,但是在條件判斷時,使用位元運算替代布爾運算卻是非常錯誤的選擇。
對於"a&&b&&c", 只要有一項返回 false, 整個運算式就返回 false.
package com.oscar999.performance.skill;public class BooleanBit {protected void oldMethod(){boolean a = false;boolean b = true;int d = 0;for(int i=0;i<10000000;i++)if(a&b&"Java_Perform".contains("Java"))d = 0;}protected void newMethod(){boolean a = false;boolean b = true;int d = 0;for(int i=0;i<10000000;i++)if(a&&b&&"Java_Perform".contains("Java"))d = 0;}/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubBooleanBit skill = new BooleanBit();long begTime = System.currentTimeMillis();skill.oldMethod();long endTime = System.currentTimeMillis();System.out.println("Old Method use: "+(endTime-begTime));begTime = System.currentTimeMillis();skill.newMethod();endTime = System.currentTimeMillis();System.out.println("New Method use: "+(endTime-begTime));}}
Old Method use: 265
New Method use: 32
9. 使用 arrayCopy()
Java API 提高了數組複製的高效方法: arrayCopy().
這個函數是 native 函數, 通常native 函數的效能要優於普通的函數, 僅出於效能考慮, 在軟體開發時, 應儘可能調用native 函數。
package com.oscar999.performance.skill;public class ArrayCopy {protected void oldMethod(){int size = 100000;int[] array = new int[size];int[] arraydst = new int[size];for(int i=0;i<array.length;i++){array[i]=i;}for(int k=0;k<1000;k++)for(int i=0;i<size;i++)arraydst[i]=array[i];}protected void newMethod(){int size = 100000;int[] array = new int[size];int[] arraydst = new int[size];for(int i=0;i<array.length;i++){array[i]=i;}for(int k=0;k<1000;k++)System.arraycopy(array, 0, arraydst, 0, size);}/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubArrayCopy skill = new ArrayCopy();long begTime = System.currentTimeMillis();skill.oldMethod();long endTime = System.currentTimeMillis();System.out.println("Old Method use: "+(endTime-begTime));begTime = System.currentTimeMillis();skill.newMethod();endTime = System.currentTimeMillis();System.out.println("New Method use: "+(endTime-begTime));}}Old Method use: 156
New Method use: 63
10. 使用 Buffer 進行 I/O 操作
除NIO 外, 使用Java 進行I/O 操作有兩種基本方式
1. 使用基於InputStream 和 OutoutStream的方式
2. 使用Writer 和Reader
無論使用哪種方式進行檔案I/O , 如果能合理地使用緩衝, 就能有效提高I/O 的效能
package com.oscar999.performance.skill;import java.io.BufferedOutputStream;import java.io.DataOutputStream;import java.io.FileOutputStream;public class BufferStream {protected void oldMethod() {int count = 10000;try {DataOutputStream dos = new DataOutputStream(new FileOutputStream("testfile.txt"));for (int i = 0; i < count; i++)dos.writeBytes(String.valueOf(i) + "\r\n");dos.close();} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}protected void newMethod(){int count = 10000;try{DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("testfile.txt")));for (int i = 0; i < count; i++)dos.writeBytes(String.valueOf(i) + "\r\n");dos.close();}catch(Exception e){e.printStackTrace();}}/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubBufferStream skill = new BufferStream();long begTime = System.currentTimeMillis();skill.oldMethod();long endTime = System.currentTimeMillis();System.out.println("Old Method use: " + (endTime - begTime));begTime = System.currentTimeMillis();skill.newMethod();endTime = System.currentTimeMillis();System.out.println("New Method use: " + (endTime - begTime));}}
Old Method use: 516
New Method use: 0
11. 使用clone() 代替new
對於重量級對象, 優於對象在建構函式中可能會進行一些複雜且耗時額操作, 因此, 建構函式的執行時間可能會比較長。Object.clone() 方法可以繞過物件建構函數, 快速複製一個對象執行個體。由於不需要調用物件建構函數, 因此, clone 方法不會受到建構函式效能的影響, 快速產生一個執行個體。
12. 靜態方法替代執行個體方法
使用static 關鍵字描述的方法為靜態方法, 在Java 中, 優於執行個體方法需要維護一張類似虛函數表的結構,以實現對多態的支援。 與靜態方法相比, 執行個體方法的調用需要更多的資源。 因此,對於一些常用的工具類方法,沒有對其進行重載的必要,那麼將它們聲明為static, 便可以加速方法的調用。
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
Java 效能最佳化系列之1[設計與程式最佳化]