優化Java中的多態代碼

jopen 9年前發布 | 17K 次閱讀 Java 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

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!