以程式碼角度最佳化
這邊僅簡單列出一些小tips,其背後的原理,佐證可參考以下書單:
我的Java程式比你的快10倍 : 從概念到工具的極度優化 \/ 葛一鳴著
Android高效能多執行緒 \/ Anders Göransson著 ; 楊仁和譯
欲提高App效能,最基本也是做簡單的辦法,就是諄照 http:\/\/developer.android.com\/training\/articles\/perf-tips.html 上所建議的小技巧。記住這些技巧,提升coding的performance。養成這些coding的習慣不外乎就是為了減少前言所歸納的:避免memory leak、減少gc過度呼叫,...等等。不要做不需要做的事,避免配置記憶體。
使用StringBuffer取代String。(String也是透過StringBuffer所實作的)
- 有必要時,也可以使用
String.format
取代String串聯的問題。(連結)
- 有必要時,也可以使用
如果需獲取字串(String)中的子字串(Sub-String),請直接從原字串呼叫substring函數做處理,而不用額外new一個重複的object。
public String get50Str(String data) { return (data.length() > 50)? data.substring(0,50):data;` }
舉個例子,假設你有一個回傳String的函式,且回傳的值是一定被append到StringBuffer的話,就不要return來建立多餘的short-lived temporary object,直接傳StringBuffer 的 reference在函式裏面動作即可。
public void append(StringBuffer sb,String apStr) { sb.append(apStr);` }
盡量使用一維陣列;假設需用到多維陣列,且請各個切割成多個醫為陣列來做為儲存容器。
而用int宣告的陣列會比Integer的陣列來的有效率。
- 因為int是屬於primitive type(基本型態),Integer是class(類別)。(牽涉到Autoboxing和Unboxing概念)。物件需要宣告實例化,且使用到cache memory後,需要GC memory,這些都是要花時間的的;所以越少GC,效能就越好。
- 同理float比Float,double對比Double。
但是在Android中,不推薦使用float-point 做運算,同樣情況下,int比float快兩倍。
- 雖然double所使用的空間是float的2倍多,但這可以忽略。若要使用浮點運算,double還是較好。
- 即使是整數,有些硬體只支援乘法,不支援除法運算。所以除法和負數運算可以以軟體實作,屆時你可能需要一個hash table和大量數學算式。
在傳遞兩個一維陣列比傳遞一個陣列附帶兩個reference object來的有效率。但要在可讀性上做些妥協。
class Object() { int Foo; int Bar; } ArrayList<Object> twoElement = .....; (沒效率) ///////////////////我是分隔線//////////////////// ArrayList<int> aElement = ...; ArrayList<int> bElement = ...; (有效率)
- 使用static比virtual快15-20%。
使用**static final**宣告常數。
- 因為被宣告為static final的常數,在app第一次被使用時,compiler會直接把變數名稱和對映的值寫到dex file中。在後續被使用時,會直接透過dex file回傳對映的value,減少memory access的時間,提升效能。
- 如果是宣告成static,會使用到memory cahce,後續有無法GC memory,進一步造成頻繁GC或是memory leak,進一步導致效能下降。
避免使用static宣告。
- 因為static的生命週期過長,使用不當導致memory leak。
避免使用getter和setter。
原因是在C++等native language中,常有getter的寫法,而不是直接去access value,因為這是C++優良的好習慣,但是Java或是C#物件島享的語言,也直接繼承這樣的寫法。然而C++有JIT(Just In Time Compiler)功能可以幫助code做性能優化功能,例如 i = getCount() 透過JIT可簡化為 i = mCount,使程式在執行時效能提高;畢竟直接access value比呼叫函式(virtual method)再取得回傳value來的快。這樣的最佳化功能在JIT只是一小部分,稱之為 inline the access。
有JIT,直接使用(inline the access) 會比getter快7倍。
- 因為JIT不只 inline the access 功能,還有...(JIT詳細介紹可參考[2])
沒有 JIT 直接使用(inline the access) 會比getter快3倍。
但是如果你有使用 ****ProGuard*\***, 就不用擔心此問題**。因為 ProGuard幫你做 inline accessors。
在普通的情況下,使用for-each效能比使用for來的好。
static class Foo {
int mSplat;
}
Foo[] mArray = ...
public void zero() {
int sum = 0;
for (int i = 0; i < mArray.length; ++i) {
sum += mArray[i].mSplat;
} }
\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
public void one() {
int sum = 0;
Foo[] localArray = mArray;
int len = localArray.length;
for (int i = 0; i < len; ++i) {
sum += localArray[i].mSplat;
} }
\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
public void two() {
int sum = 0;
for (Foo a : mArray) {
sum += a.mSplat;
} }
zero()是最慢的,因為JIT沒有辦法對它做最佳化。
one()稍微快些。
two() 在沒有做JIT時是最快的,可是如果經過JIT之後,與方法one()是差不多一樣快的。它使用了增強的循環方法for-each。
所以請儘量使用for-each的方法,但是對於ArrayList,請使用方法one()。
Consider Package Instead of Private Access with Private Inner Classes
- 不太懂其意思,等待好心人解釋。
建議使用系統內建的library method,其中 System.arraycopy()) 是手寫 loop的9倍快。
使用Native Code要小心,有些 Native method無法被JIT最佳化。
避免使用Enum。運行時,會額外產生memory access,不可忽視。[3]
假如我們有一個基本的app,經過compiler後,dex大小是2556bytes,在這基準上,增加以下code,以final static常數作為判斷比較值:
- 增加以下code經過compiler後,dex是2680 bytes,對照原本的code,只增加124bytes。
假如是使用enum的情況呢,會是如何?
- 使用enmu後的dex大小是4188 bytes,對照一開始的codey足足增加了1632 bytes(4188-2556),增長量是使用static int的13倍。
不僅僅如此,使用enum,運行時還會產生額外的memory 空間佔用,如下圖所示。
解決辦法:
- 【方法一】使用IntDef annotation改寫
- 【方法二】使用ProGuard會自動壓縮enum,使其最佳化。
慎選資料容器。例如HashMap .ArrayMap SparaseArray...
部分範例參考至 ref:
http:\/\/leehom59.blogspot.tw\/2011\/02\/basic-concept-about-java-coding-in.html
[2]http:\/\/blog.dontcareabout.us\/2013\/03\/jit-compiler.html
JIT
還沒作最佳化的程式碼:
class Calculator { Wrapper wrapper; public void calculate() { y = wrapper.get(); z = wrapper.get(); sum = y + z; } } class Wrapper { final int value; final int get() { return value; } }
最佳化過後的程式碼:
class Calculator { Wrapper wrapper; public void calculate() { y = wrapper.value; sum = y + y; } } class Wrapper { final int value; final int get() { return value; } }
第一個 class 是開發人員寫的例子,第二個是 JIT 處理過後的樣本。 這裡頭包含了幾種最佳化的技術。讓我們來看一下最終結果是怎麼做到的:
還沒最佳化的程式碼。在還沒被判定成 hot spot 時,程式碼會這樣執行:
class Calculator { Wrapper wrapper; public void calculate() { y = wrapper.get(); z = wrapper.get(); sum = y + z; } }
inline method:用
b.value
取代wrapper.get()
, 不透過函數呼叫而直接存取wrapper.value
來減少延遲。class Calculator { Wrapper wrapper; public void calculate() { y = wrapper.value; z = wrapper.value; sum = y + z; } }
移除多餘的載入:用
z = y
取代z = wrapper.value
, 所以只存取區域變數而不是wrapper.value
來減少延遲。class Calculator { Wrapper wrapper; public void calculate() { y = wrapper.value; z = y; sum = y + z; } }
copy propagation:用
y = y
取代z = y
, 沒有必要再用一個變數z
,因為z
跟y
會是相等的。class Calculator { Wrapper wrapper; public void calculate() { y = wrapper.value; y = y; sum = y + y; } }
消除不用的程式碼:
y = y
是不必要的,可以消滅掉。class Calculator { Wrapper wrapper; public void calculate() { y = wrapper.value; sum = y + y; } }