web-dev-qa-db-ja.com

JavaのHashMap、1億エントリ

1億の用語とその頻度(テキストデータベース)をHashMap <String, Double>に保存します。 「メモリ不足」エラーが表示されます。ヒープスペースを-Xmx15000Mに増やすことを試みました。ただし、30分実行し、同じ例外を再度スローします。単語と頻度を読み取ろうとしているファイルサイズは1.7GBです。

どんな助けも大歓迎です。

ありがとう:-)

27
ablimit

このようなWord処理の場合、長いルックアップ時間で対応できる場合、答えは通常、ハッシュマップではなくツリーです。この構造は、多くの単語に共通の開始文字列がある自然言語の場合、非常にメモリ効率が高くなります。

入力によっては、パトリシアツリーの方がさらに良い場合があります。

(また、これが実際に自然言語の単語である場合、100,000,000のエントリが本当に必要ですか?一般的に使用される単語の大部分は驚くほど低く、商用ソリューション(単語予測、スペル修正)は、言語に関係なく100,000単語以上をめったに使用しません)

16
Christoffer

あなたの問題は、個々の文字列オブジェクトによって追加されるオーバーヘッドがなくても、1.7 GBのrawテキストが1500 MBを超えることです。巨大なマッピングの場合、データベースまたはファイルでバックアップされたマップのいずれかを使用する必要があります。これらはヒープではなくディスクメモリを使用します。

更新

ほとんどのjvmでは、ヒープに15 GBを割り当てることはできないと思います。 32ビットのJVMでは機能せず、64ビットのJVMでも機能するとは思わない。 十分なRAMが使用可能な場合、64ビットjvmで15 GBのメモリが動作するはずです。

11
josefx

1億の用語を使用すると、ほぼ確実に、メモリ内に格納する必要のある制限を超えています。何らかの種類のデータベースに用語を保存します。商用データベースを使用するか、ファイルにアクセスして必要な情報を取得できるものを作成します。使用しているファイル形式ですぐにファイルにアクセスできない場合は、そのファイル形式に変換します。たとえば、各レコードを固定サイズにして、レコード番号のファイルオフセットを即座に計算できるようにします。レコードをソートすると、バイナリ検索を非常に迅速に実行できます。また、ファイル全体をメモリに保存することなく、ファイルへのアクセスを大幅に高速化するコードを作成できます。

5
DJClayworth

1.7 GBファイルは、これを実行してRAMに保存する比較的小さなファイルです。これをはるかに大きなファイルで行い、問題なくメモリに保存します。データベースを使用することもできますが、データをどのように処理するかによって、使いすぎたり、完璧になったりする場合があります。

他の人が言ったように、自然言語では、一意の値の数は比較的少ない可能性が高いため、マップは実際にはそれほど大きくなりません。私はJava.util.HashMapをそのまま使用しません メモリの点で非常に非効率的 特にintなどのプリミティブ値を格納する場合の使用。 Java.util.HashMapは、プリミティブをオブジェクトとして保存します。また、メモリを浪費するHashMap.Entryオブジェクト内に各値を格納します。これらの2つの要因により、Java.util.HashMapは TroveFastutil などの代替手段よりも多くのメモリを使用します。

前述のように、これらの問題のないマップの実装がいくつかあります。マップに数値を格納しているため、マップに新しい値を入力したり古い値を更新したりするときにオブジェクトとプリミティブを絶えず切り替える(つまり、ボクシング/アンボクシング)必要がないため、パフォーマンスが向上します値。大量のデータにより適したさまざまなプリミティブハッシュマップのベンチマークを見つけることができます この投稿のJava Performance Tuning Guide

5
Jay Askren

軽量のKeyValue(マップ)ストアだけが必要な場合は、Redisの使用を検討します。それは非常に高速で、必要に応じてデータを永続化する機能を備えています。唯一の欠点は、LinuxマシンでRedisストアを実行する必要があることです。

Windowsに限定されている場合、64ビットで実行できるMongoDBは良いオプションです。

4
Joshua

ステミングを試して、重複の数を増やすこともできます。

たとえば、cat = Cats = cats = Cat

または

泳ぐ=泳ぐ=泳ぐ

グーグル「Porter Stemmer」を試してください

2
Ivan

他の答えは、問題がメモリ使用量にあることをすでに指摘しています。問題のドメインに応じて、全体的なメモリフットプリントを削減するキークラスを設計できます。たとえば、キーが自然言語のフレーズで構成されている場合、フレーズを構成する単語を分離してインターンできます。例えば.

public class Phrase {
  private final String[] interned;

  public Phrase(String phrase) {
    String[] tmp = phrase.split(phrase, "\\s");

    this.interned = new String[tmp.length];

    for (int i=0; i<tmp.length; ++i) {
      this.interned[i] = tmp[i].intern();
    }
  }

  public boolean equals(Object o) { /* TODO */ }
  public int hashCode() { /* TODO */ }
}

実際、文字列が自然言語を表していない場合でも、このソリューションは機能する可能性があります。ただし、文字列間で悪用される可能性のある大きな重複がある場合に限ります。

1
Adamski

Trove THashMapは、使用するメモリを大幅に削減します。それでも、サイズを小さくするだけで十分かどうかは疑問です。厳密にメモリに保存する以外に、取得のためにこの情報を保存するために別の場所が必要です。

1
AHungerArtist

HashMapをドロップし、そのすべてのデータをHBaseまたは他のNoSQLデータストアのいずれかにロードし、 MapReduce 操作の観点からクエリを記述します。これは、Google検索や、大量のデータを扱う他の多くのサイトで採用されているアプローチです。基本的に無限のサイズにスケーリングすることが実証されています。

1
Barend

cdb で置き換えることを検討してください。最大4 GBおよび:

大規模なデータベースで正常に検索するには、通常2回のディスクアクセスが必要です。失敗したルックアップには1つしかかかりません。

0
whiskeysierra

Terracottaからの興味深い提供があります- BigMemory これはまさにあなたが望んでいるもののようです。私は自分で試したことはありませんが、ライセンス条件などは知りません。

0
maximdim

封筒の裏側:1.7Gb/100M =平均18バイト=用語および周波数ごと

2つの論理配列に支えられたハンドコーディングされたハッシュマップを使用できます。

  1. 1つはint頻度(値)を保持し、もう1つはCスタイルのchar配列を作成して、2次元のc配列(char配列の配列)をシミュレートすることです。計算によってインデックスを作成します。 Java 2次元配列はオブジェクトのオーバーヘッドが大きすぎるため使用できません。このchar配列は、キーを表す固定サイズのchar配列を保持できます。したがって、キーのハッシュを計算してこの "2次元配列"内にあり、競合がある場合は、たとえば線形プローブによって解決できますキーと値のペアは、配列の共通インデックスによって結び付けられています。

  2. ハッシュマップでは、チェーン用の十分なメモリがないため、オープンアドレス指定を使用する必要があります。

  3. キーの長さに基づいて、このハッシュマップのインスタンスを10個持つことができます。データの特性がわからないので、確信が持てません。

  4. 使用されるスペース= int配列の2乗29 +(2乗4(文字列ごとに16バイト)* 2 pow 27)= 3.5ギガ

  5. Intではなく倍の周波数が必要な場合は、文字列のサイズを適切に小さくする必要があります。

0
Ranga Baireddy

Javaでは、他のどのコンテンツを保持するかを考慮する前に、オブジェクトには最小サイズとして16バイトのオーバーヘッドがあります。

ハッシュマップ内の1e8アイテムには1e8 * 2 * 16バイトという過小評価されたサイズ要件があり、キーと値が数値であると想定しているため、ヒープおよびコンピューターから使用可能なヒープが数GB必要です。

文字列は文字配列を保持するオブジェクトであるため、上記で説明したように、たとえば文字列はDoubleオブジェクトよりも大きくなる可能性があるため、ヒープに使用できるメモリがさらに必要になります。

コンピュータの限界に近づくと、プログラムのパフォーマンスが低下し始めることに注意してください。

上記のようにデータベースを使用したくない場合は、キーをエンコードおよび圧縮して、頻度をカウントできる数字に変換することを検討できます。その最初のエンコーディングの単語の頻度に基づいてエントロピーベースのエンコーディングを選択し、そこから進むことができます...

0
nichole

その悪いデザイン。 HashMapのメモリに1.7GBのデータがある場合、次の2つのいずれかを実行します。

  1. すべてのデータ(ファイル/データベース)を保持し、上位1%または何かをメモリに保持します。どのIDをいつメモリに格納するかを決定するためのアルゴリズムを使用します。

  2. memcached を使用します。最も簡単な方法。インメモリ分散ハッシュ可能。これがまさにDHTの使用目的です。

0
zengr