簡體   English   中英

優化java.util.Map和java.util.Set的實現?

[英]Optimized implementations of java.util.Map and java.util.Set?

我正在編寫一個應用程序,其中內存以及在較小程度上的速度至關重要。 我從剖析中發現,我花了很多時間在Map和Set操作中。 雖然我在考慮減少調用這些方法的方法,但我想知道是否有人在編寫或遇到過顯着改進訪問時間或內存開銷的實現? 或者至少,在某些假設的情況下,這可以改善這些事情嗎?

從JDK源代碼來看,我無法相信它不能更快​​或更精簡。

我知道Commons Collections,但我不相信它有任何實現,其目標是更快或更精簡。 Google Collections也是如此。

更新:應該注意到我不需要線程安全。

通常這些方法非常快。 您應該檢查幾件事情:您的哈希碼是否已實施? 它們是否足夠均勻? 否則你會得到垃圾表現。

http://trove4j.sourceforge.net/ < - 這有點快,節省了一些內存。 我在50,000次更新時節省了幾毫秒

您確定正確使用地圖/套裝嗎? 即不試圖迭代所有的值或類似的東西。 另外,例如,不要執行包含然后刪除。 只需檢查刪除。

還要檢查你是否使用Double vs double。 我注意到,在成千上萬次檢查中,性能有了幾分之一的提升。

您是否也正確/適當地設置了初始容量?

你看過Trove4J嗎? 來自網站:

Trove旨在提供java.util.Collections API的快速輕量級實現。

基准在這里提供。

除Google和Commons Collections外,以下是我所知道的:

當然,您始終可以實現自己的數據結構,這些結構針對您的用例進行了優化。 為了能夠更好地提供幫助,我們需要了解您訪問模式以及您在集合中存儲的數據類型。

嘗試提高equals和hashCode方法的性能,這有助於加快標准容器對象的使用速度。

您可以擴展AbstractMap和/或AbstractSet作為起點。 我不久前做了這個來實現基於二元trie的映射(鍵是一個整數,樹上的每個“級別”都是一個位置。左子是0,右子是1)。 這對我們來說效果很好,因為密鑰是EUI-64標識符,對我們來說大多數時候前5個字節都是相同的。

要實現AbstractMap,您至少需要實現entrySet()方法,以返回一組Map.Entry,每個Map.Entry都是一個鍵/值對。

要實現集合,可以擴展AbstractSet並提供size()和iterator()的實現。

然而,這至少是這樣。 您還需要實現get和put,因為默認映射是不可修改的,並且get的默認實現迭代通過entrySet查找匹配項。

您可以通過以下方式節省一點內存:

(a)使用更強大,更寬的哈希碼 ,從而避免必須存儲密鑰 ;

(b)通過從數組中分配自己, 避免為每個哈希表條目創建單獨的對象

如果它有用,這里是一個簡單的數字Recipies哈希表的Java實現,我有時會發現它很有用。 您可以直接鍵入CharSequence(包括字符串),否則您必須自己為對象提供強大的64位哈希函數。

請記住,此實現不存儲密鑰 ,因此如果兩個項具有相同的哈希代碼(如果您具有良好的哈希函數,則在2 ^ 32的順序哈希之后可以預期,或者如果您具有良好的哈希函數則需要幾十億個項)然后一個項目將覆蓋另一個項目:

public class CompactMap<E> implements Serializable {
  static final long serialVersionUID = 1L;

  private static final int MAX_HASH_TABLE_SIZE = 1 << 24;
  private static final int MAX_HASH_TABLE_SIZE_WITH_FILL_FACTOR = 1 << 20;

  private static final long[] byteTable;
  private static final long HSTART = 0xBB40E64DA205B064L;
  private static final long HMULT = 7664345821815920749L;

  static {
    byteTable = new long[256];
    long h = 0x544B2FBACAAF1684L;
    for (int i = 0; i < 256; i++) {
      for (int j = 0; j < 31; j++) {
        h = (h >>> 7) ^ h;
        h = (h << 11) ^ h;
        h = (h >>> 10) ^ h;
      }
      byteTable[i] = h;
    }
  }

  private int maxValues;
  private int[] table;
  private int[] nextPtrs;
  private long[] hashValues;
  private E[] elements;
  private int nextHashValuePos;
  private int hashMask;
  private int size;

  @SuppressWarnings("unchecked")
  public CompactMap(int maxElements) {
    int sz = 128;
    int desiredTableSize = maxElements;
    if (desiredTableSize < MAX_HASH_TABLE_SIZE_WITH_FILL_FACTOR) {
      desiredTableSize = desiredTableSize * 4 / 3;
    }
    desiredTableSize = Math.min(desiredTableSize, MAX_HASH_TABLE_SIZE);
    while (sz < desiredTableSize) {
      sz <<= 1;
    }
    this.maxValues = maxElements;
    this.table = new int[sz];
    this.nextPtrs = new int[maxValues];
    this.hashValues = new long[maxValues];
    this.elements = (E[]) new Object[sz];
    Arrays.fill(table, -1);
    this.hashMask = sz-1;
  }

  public int size() {
    return size;
  }

  public E put(CharSequence key, E val) {
    return put(hash(key), val);
  }

  public E put(long hash, E val) {
    int hc = (int) hash & hashMask;
    int[] table = this.table;
    int k = table[hc];
    if (k != -1) {
      int lastk;
      do {
        if (hashValues[k] == hash) {
          E old = elements[k];
          elements[k] = val;
          return old;
        }
        lastk = k;
        k = nextPtrs[k];
      } while (k != -1);
      k = nextHashValuePos++;
      nextPtrs[lastk] = k;
    } else {
      k = nextHashValuePos++;
      table[hc] = k;
    }
    if (k >= maxValues) {
      throw new IllegalStateException("Hash table full (size " + size + ", k " + k);
    }
    hashValues[k] = hash;
    nextPtrs[k] = -1;
    elements[k] = val;
    size++;
    return null;
  }

  public E get(long hash) {
    int hc = (int) hash & hashMask;
    int[] table = this.table;
    int k = table[hc];
    if (k != -1) {
      do {
        if (hashValues[k] == hash) {
          return elements[k];
        }
        k = nextPtrs[k];
      } while (k != -1);
    }
    return null;
  }

  public E get(CharSequence hash) {
    return get(hash(hash));
  }

  public static long hash(CharSequence cs) {
    if (cs == null) return 1L;
    long h = HSTART;
    final long hmult = HMULT;
    final long[] ht = byteTable;
    for (int i = cs.length()-1; i >= 0; i--) {
      char ch = cs.charAt(i);
      h = (h * hmult) ^ ht[ch & 0xff];
      h = (h * hmult) ^ ht[(ch >>> 8) & 0xff];
    }
    return h;
  }

}

在commons-collections中至少有一個專門為速度而構建的實現: Flat3Map它非常具體,因為只要不超過3個元素它就會非常快。

我懷疑你可以通過跟隨@ thaggie的建議添加看看equals / hashcode方法時間來獲得更多的變化。

你說你描述了一些課程,但你有沒有時間檢查他們的速度? 我不確定你是如何檢查他們的內存使用情況的。 當您比較不同的實現時,似乎有一些特定的數字會很好。

這里有一些注釋和幾個替代數據結構庫的鏈接: http//www.leepoint.net/notes-java/data/collections/ds-alternatives.html

我也會對fastutil進行強烈投票。 (在另一個回復中提到,並在該頁面上)它具有比你可以動搖的更多不同的數據結構,以及針對基本類型優化的版本作為鍵或值。 (缺點是jar文件很大,但你可以把它修剪成你需要的)

幾年前我經歷過這樣的事情 - 非常大的地圖和集合以及其中很多。 默認的Java實現占用了太多空間。 最后我推出了自己的,但只是在我檢查了我的代碼所需的實際使用模式之后。 例如,我有一組已知的大型對象,這些對象是早期創建的,有些地圖很稀疏而有些地圖很密集。 其他結構單調增長(沒有刪除),而在其他地方,使用“集合”更快,處理重復項目的偶爾但無害的額外工作比花費時間和空間避免重復。 我使用的許多實現都是數組支持的,並且利用了我的哈希碼被順序分配的事實,因此對於密集映射,查找只是一個數組訪問。

帶走消息:

  1. 看看你的算法,
  2. 考慮多個實現,和
  3. 請記住,大多數庫都適用於通用目的(例如插入刪除,一系列大小,既不稀疏也不密集等),因此它們可能會有可能避免的開銷。

哦,寫單元測試......

有時當我看到Map和Set操作使用高百分比的CPU時,它表明我已經過度使用了Map和Set,並且我的數據重組幾乎已經消除了來自前10%CPU消費者的集合。

查看是否可以避免集合的副本,迭代集合以及導致訪問集合的大多數元素和創建對象的任何其他操作。

我使用下面的包(koloboke)來做一個int-int hashmap,因為它支持promitive類型,並且它在一個long變量中存儲了兩個int,這對我來說很酷。 koloboke

它可能不是導致問題的MapSet ,而是它們背后的對象。 根據您的問題,您可能需要更多數據庫類型的方案,其中“對象”存儲為一堆字節而不是Java對象。 你可以嵌入一個數據庫(比如Apache Derby)或做你自己的專家。 這非常依賴於你實際在做什么。 HashMap並不是故意大而慢......

Commons Collections有FastArrayListFastHashMapFastTreeMap,但我不知道它們的價值......

  • Commons Collections有一個id映射,通過==進行比較,它應該更快。 - [Joda Primities][1]和原始集合一樣,Trove也是如此。 我對Trove進行了實驗,發現其內存使用情況更好。
  • 我用幾個整數來映射許多小對象的集合。 將這些更改為整數可以節省近一半的內存(盡管需要一些更復雜的應用程序代碼來補償)。
  • 對我來說,排序的樹應該比hashmaps消耗更少的內存似乎是合理的,因為它們不需要加載因子(盡管如果有人可以確認或有理由為什么這實際上是愚蠢的,請在評論中發布)。

您使用的是哪個版本的JVM?

如果你不在6歲(雖然我懷疑你是),那么切換到6可能會有所幫助。

如果這是一個服務器應用程序並在Windows上運行,請嘗試使用-server來使用正確的熱點實現。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM