優化Java中的多態代碼
優化Java中的多態代碼
Oracle的Java是一個門快速的語言,有時候它可以和C++一樣快。編寫Java代碼時,我們通常使用接口、繼承或者包裝類(wrapper class)來實現多態,使軟件更加靈活。不幸的是,多態會引入更多的調用,讓Java的性能變得糟糕。部分問題是,Java不建議使用完全的內聯代碼,即使它是非常安全的。(這個問題可能會在最新的Java版本里得到緩解,請看文章后面的更新部分)
考慮下這種情況,我們要用接口抽象出一個整型數組:
public interface Array { public int get(int i); public void set(int i, int x); public int size(); }
你為什么要這樣做?可能是因為你的數據是保存在數據庫里、網絡上、磁盤上或者在其他的數據結構里。你想一次編碼后就不用關心數組的具體實現。
編寫一個與標準Java數組一樣高效率的類并不難,不同之處在于它實現了這個接口:
public final class NaiveArray implements Array { protected int[] array; public NaiveArray(int cap) { array = new int[cap]; } public int get(int i) { return array[i]; } public void set(int i, int x) { array[i] = x; } public int size() { return array.length; } }
至少在理論上,NaiveArray類不會出現任何的性能問題。這個類是final的,所有的方法都很簡短。
不幸的是,在一個簡單的benchmark類里,當使用NavieArray作為數組實例時,你會發現NavieArray比標準數組慢5倍以上。就像這個例子:
public int compute() { for(int k = 0; k < array.size(); ++k) array.set(k,k); int sum = 0; for(int k = 0; k < array.size(); ++k) sum += array.get(k); return sum; }
你可以通過使用NavieArray作為NavieArray的一個實例來稍微減緩性能問題(避免使用多態)。不幸的是,它依然會慢3倍多。而你僅是放棄了多態的好處。
那么,強制使用內聯函數調用會怎樣?
一個可行的解決方法是手動實現內聯函數。你可以使用 instanceof 關鍵字來提供優化實現,否則你只會得到一個普通(更慢)的實現。例如,如果你使用下面的代碼,NavieArray就會變得和標準數組一樣快:
public int compute() { if(array instanceof NaiveArray) { int[] back = ((NaiveArray) array).array; for(int k = 0; k < back.length; ++k) back[k] = k; int sum = 0; for(int k = 0; k < back.length; ++k) sum += back[k]; return sum; } //... }
當然,我也會介紹一個維護問題作為需要實現不止一次的同類算法…… 當出現性能問題時,這是一個可接受的替代。
和往常一樣,我的benchmarking代碼可以在網上獲取到。
總結
- 一些Java版本可能不完全支持頻繁的內聯函數調用,即使它可以并且應該支持。這會造成嚴重的性能問題。
- 把類聲明為 final 看起來不會緩解性能問題。
- 對于消耗大的函數,可行的解決方法是自己手動優化多態和實現內聯函數調用。使用 instanceof 關鍵字,你可以為一些特定的類編寫代碼并且(因此)保留多態的靈活性。
更新
Erich Schubert使用 double 數組運行簡單的benchmark類發現他的運行結果與我的結果相矛盾,而且我們的變量實現都是一樣的。我通過更新到最新版本的OpenJDK證明了他的結果。下面的表格給出了處理10百萬整數需要的納秒時間:
Function | Oracle JDK 8u11 | OpenJDK 1.8.0_40 | OpenJDK 1.7.0_65 |
---|---|---|---|
straight arrays | 0.92 | 0.71 | 0.87 |
with interface | 5.9 | 0.70 | 6.3 |
with manual inlining | 0.98 | 0.71 | 0.93 |
正如我們看到的,最新版本的OpenJDK十分智能,并且消除了多態的性能開銷(1.8.0_40)。如果你足夠幸運地在使用這個JDK,你不需要擔心這 篇文章所說的性能問題。但是,這個總體思想依然值得應用在更復雜的場景里。例如,JDK優化可能依然達不到你期待的性能要求。
原文鏈接: lemire 翻譯: ImportNew.com - 進林
譯文鏈接: http://www.importnew.com/14393.html