2017-01-25 63 views
1

我使用HashMap緩存通過遞歸算法計算出的約200萬個值。我使用集合框架中的HashMap<Integer, Double>或者Trove庫中的TIntDoubleHashMap,由boolean useTrove變量控制,如下面的代碼所示。使用標準Java HashMap(與Trove THashMap相比)導致非HashMap代碼運行速度更慢

我不指望特羅韋庫要快,因爲它比500ms左右的HashMap<>避免自動裝箱等。而實際上,put()get()通話大約需要300毫秒運行(總量)爲THashMap

現在,使用THashMap時,我的整體程序運行時間大約爲2.8秒,使用HashMap<>時爲6.7秒。單獨調用put()get()調用的運行時間增加不能解釋這種差異。

  1. 我懷疑這跟HashMap<>大大增加運行時間的事實,這種實現是相當內存低效的,因爲 每個INT /雙需要被裝箱爲對象,這增加了驅動 內存使用會導致緩存在節目的其他部分錯過。 這個解釋是否有意義,我如何確認/拒絕這個 假設?

  2. 一般來說,我該如何探索這些場景的算法優化?對算法進行剖析並不容易指出是罪魁禍首,至少如果單獨考慮CPU時間是 。這僅僅是一個事先知道的問題:內存使用需要優先考慮內存飢渴的程序嗎?

代碼如下。

import java.util.HashMap; 
import gnu.trove.map.hash.TIntDoubleHashMap; 

class RuntimeStopWatch { 
    long elapsedTime; 
    long startTime; 
    RuntimeStopWatch() { reset(); } 
    void reset() { elapsedTime = 0; } 
    void start() { startTime = System.nanoTime(); } 
    void stop() { 
     long endTime = System.nanoTime(); 
     elapsedTime += (endTime - startTime); 
     startTime = endTime; 
    } 
    void printElapsedTime(String prefix) { 
     System.out.format(prefix + "%dms\n", elapsedTime/1000000); 
    } 
} 

public class HashMapBehaviour { 

    static RuntimeStopWatch programTime = new RuntimeStopWatch(); 
    static RuntimeStopWatch hashMapTime = new RuntimeStopWatch(); 
    static HashMap<Integer, Double> javaHashMapCache; 
    static TIntDoubleHashMap troveHashMapCache; 
    static boolean useTrove; 

    public static void main(String[] args) { 
//  useTrove = true; 
     useTrove = false; 

     javaHashMapCache = new HashMap<>(); 
     troveHashMapCache = new TIntDoubleHashMap(); 

     programTime.start(); 
     recursiveFunction(29, 29, 178956970); 
     programTime.stop(); 

     programTime.printElapsedTime("Program: "); 
     hashMapTime.printElapsedTime("Hashmap: "); 
    } 


    static double recursiveFunction(int n, int k, int bitString) { 
     if (k == 0) return 0.0; 
     if (useTrove) { 
      hashMapTime.start(); 
      if (troveHashMapCache.containsKey(bitString | (1 << n))) return troveHashMapCache.get(bitString | (1 << n)); 
      hashMapTime.stop(); 
     } else { 
      hashMapTime.start(); 
      if (javaHashMapCache.containsKey(bitString | (1 << n))) return javaHashMapCache.get(bitString | (1 << n)); 
      hashMapTime.stop(); 
     } 
     double result = 0.0; 
     for (int i = 0; i < (n >> 1); i++) { 
      double play1 = recursiveFunction(n - 1, k - 1, stripSingleBit(bitString, i)); 
      double play2 = recursiveFunction(n - 1, k - 1, stripSingleBit(bitString, n - i - 1)); 
      result += Math.max(play1, play2); 
     } 
     if (useTrove) { 
      hashMapTime.start(); 
      troveHashMapCache.put(bitString | (1 << n), result); 
      hashMapTime.stop(); 
     } else { 
      hashMapTime.start(); 
      javaHashMapCache.put(bitString | (1 << n), result); 
      hashMapTime.stop(); 
     } 
     return result; 
    } 

    static int stripSingleBit(int bitString, int bitIndex) { 
     return ((bitString >> (bitIndex + 1)) << bitIndex) | (bitString & ((1 << bitIndex) - 1)); 
    } 
} 

回答

0

Trove的一件大事就是您要預先設定收藏大小。由於存儲在T * Maps中是基於單個數組的,因此無法預先設置集合的大小將導致大量的數組創建和複製。 HashMap沒有這個問題,因爲它使用鏈接的對象。

所以,總結:嘗試用new TIntDoubleHashMap(<expected_size>)

調整您的收藏在一個宏大的範圍,想想自己優化的內容。 Trove對整體內存使用率和性能有最高的效率。然而,巨大的性能收益並非來自超時髦的哈希算法,而是因爲使用較少的臨時對象(用於裝箱),所以GC壓力可能較低。這對您是否重要完全取決於您的應用程序。此外,加載因子還允許您以查找速度爲代價來折衷陣列中的數據「密度」。所以,調整可以是有用的。如果在查找過程中遇到大量衝突並希望獲得更好的性能或想要以性能爲代價來最大化內存,請調整因子。

如果您有內存需要刻錄並且只需要查找性能,HashMap非常難以擊敗......特別是如果地圖內容是靜態的。 JVM非常擅長優化臨時對象,所以不要過快地打折。 (過早優化等)

請記住,這種微型基準測試並不一定是現實世界性能的一個很好的指標。它錯過了諸如GC壓力和JIT編譯之類的東西。像JMH這樣的工具可以幫助編寫更具代表性的測試。

相關問題