12

キーが50ビット整数で値が32ビット(最大)整数である1億1100万のキーと値のペア(1つのキーは複数の値を持つことができます-最大2/3)を格納しています。今、私の要件は次のとおりです。

  1. (キー、値)ペアの高速挿入[重複を許可]
  2. キーに基づく値の高速取得。

ここでは、MultiMapに基づいた優れたソリューションを示します。ただし、パフォーマンスを少しでも低下させずに、より多くのキーと値のペアをメインメモリに格納したいと思います。私はWeb記事から、B +ツリー、R +ツリー、Bツリー、コンパクトマルチマップなどがそのための優れたソリューションになる可能性があることを学びました。誰かが私を助けることができます:

私のすべてのニーズを適切に満たすJavaライブラリはありますか(上記/他のdsも受け入れられます。問題はありません)?実際、私は効率的なJavaライブラリデータ構造でキーと値/値のペアを格納/取得する必要があります。これにより、メモリフットプリントが少なくて済み、メモリ内に構築する必要があります。

NB:Louis Wasserman、Kyoto / Tokyo Cabinetなどで言及されているように、HashMultiMap(Guavaにトローブをいくつか変更したもの)を試してみました。ディスクベイクドソリューションでは、私の経験は良くありません。だからそれを避けてください:)。もう1つのポイントは、library / dsを選択する際の重要なポイントの1つは、キーが50ビット(64ビットを割り当てる場合)14ビットが失われ、値が32ビットInt(最大)であるということです。ほとんどの場合、10-12-14ビットです。そのため、そこでもスペースを節約できます。

4

6 に答える 6

7

JDKにはこれを行うものはないと思います。

しかし、そのようなことを実装することはプログラミングの単純な問題です。キーと値が並列配列に格納された、線形プロービングを使用したオープンアドレスのハッシュテーブルを次に示します。

public class LongIntParallelHashMultimap {

    private static final long NULL = 0L;

    private final long[] keys;
    private final int[] values;
    private int size;

    public LongIntParallelHashMultimap(int capacity) {
        keys = new long[capacity];
        values = new int[capacity];
    }

    public void put(long key, int value) {
        if (key == NULL) throw new IllegalArgumentException("key cannot be " + NULL);
        if (size == keys.length) throw new IllegalStateException("map is full");

        int index = indexFor(key);
        while (keys[index] != NULL) {
            index = successor(index);
        }
        keys[index] = key;
        values[index] = value;
        ++size;
    }

    public int[] get(long key) {
        if (key == NULL) throw new IllegalArgumentException("key cannot be " + NULL);

        int index = indexFor(key);
        int count = countHits(key, index);

        int[] hits = new int[count];
        int hitIndex = 0;

        while (keys[index] != NULL) {
            if (keys[index] == key) {
                hits[hitIndex] = values[index];
                ++hitIndex;
            }
            index = successor(index);
        }

        return hits;
    }

    private int countHits(long key, int index) {
        int numHits = 0;
        while (keys[index] != NULL) {
            if (keys[index] == key) ++numHits;
            index = successor(index);
        }
        return numHits;
    }

    private int indexFor(long key) {
        // the hashing constant is (the golden ratio * Long.MAX_VALUE) + 1
        // see The Art of Computer Programming, section 6.4
        // the constant has two important properties:
        // (1) it is coprime with 2^64, so multiplication by it is a bijective function, and does not generate collisions in the hash
        // (2) it has a 1 in the bottom bit, so it does not add zeroes in the bottom bits of the hash, and does not generate (gratuitous) collisions in the index
        long hash = key * 5700357409661598721L;
        return Math.abs((int) (hash % keys.length));
    }

    private int successor(int index) {
        return (index + 1) % keys.length;
    }

    public int size() {
        return size;
    }

}

これは固定サイズの構造であることに注意してください。すべてのデータを保持するのに十分な大きさで作成する必要があります。私にとって1億1,000万エントリは1.32GBを占めます。データを保存するために必要なものを超えて大きくすると、挿入とルックアップが速くなります。負荷率が0.5(2.64 GB、必要なスペースの2倍)の1億1,000万エントリの場合、キーを検索するのに平均403ナノ秒かかりましたが、負荷率は0.75(1.76 GB、必要なスペースの3分の1)、575ナノ秒かかりました。通常、負荷率を0.5未満に下げても大きな違いはありません。実際、負荷率が0.33(4.00 GB、必要なスペースの3倍)の場合、平均時間は394ナノ秒になります。したがって、5 GBが使用可能であっても、すべてを使用しないでください。

ゼロはキーとして許可されていないことにも注意してください。これが問題になる場合は、null値を別の値に変更し、作成時にキー配列にその値を事前に入力します。

于 2012-04-08T23:00:41.647 に答える
2

これらすべてのニーズを適切に満たすJavaライブラリはありますか。

AFAIK番号 または、少なくとも、メモリフットプリントを最小化するものではありません。

ただし、これらの要件に特化したカスタムマップクラスを簡単に作成できるはずです。

于 2012-04-08T16:47:33.330 に答える
2

このような問題はデータベースの目的であるため、データベースを探すことをお勧めします。近年、Key-Valueデータベースは、たとえばWebサービス(キーワード「NoSQL」)などで非常に人気が高まっているため、何かを見つける必要があります。

カスタムデータ構造の選択は、ハードドライブを使用してデータを保存するかどうか(およびデータの安全性)、またはプログラムの終了時にデータが完全に失われるかどうかによっても異なります。

手動で実装し、データベース全体がメモリにいくらか簡単に収まる場合は、Cでハッシュマップを実装するだけです。値から(十分に分散された)メモリアドレスを与えるハッシュ関数を作成します。すでに割り当てられている場合は、そこまたは横に挿入します。その場合、割り当てと取得はO(1)です。Javaで実装すると、(プリミティブ)オブジェクトごとに4バイトのオーバーヘッドが発生します。

于 2012-04-08T17:01:34.027 に答える
2

@Tom Andersonsソリューションに基づいて、オブジェクトを割り当てる必要をなくし、パフォーマンステストを追加しました。

import java.util.Arrays;
import java.util.Random;

public class LongIntParallelHashMultimap {
    private static final long NULL = Long.MIN_VALUE;

    private final long[] keys;
    private final int[] values;
    private int size;

    public LongIntParallelHashMultimap(int capacity) {
        keys = new long[capacity];
        values = new int[capacity];
        Arrays.fill(keys, NULL);
    }

    public void put(long key, int value) {
        if (key == NULL) throw new IllegalArgumentException("key cannot be " + NULL);
        if (size == keys.length) throw new IllegalStateException("map is full");

        int index = indexFor(key);
        while (keys[index] != NULL) {
            index = successor(index);
        }
        keys[index] = key;
        values[index] = value;
        ++size;
    }

    public int get(long key, int[] hits) {
        if (key == NULL) throw new IllegalArgumentException("key cannot be " + NULL);

        int index = indexFor(key);

        int hitIndex = 0;

        while (keys[index] != NULL) {
            if (keys[index] == key) {
                hits[hitIndex] = values[index];
                ++hitIndex;
                if (hitIndex == hits.length)
                    break;
            }
            index = successor(index);
        }

        return hitIndex;
    }

    private int indexFor(long key) {
        return Math.abs((int) (key % keys.length));
    }

    private int successor(int index) {
        index++;
        return index >= keys.length ? index - keys.length : index;
    }

    public int size() {
        return size;
    }

    public static class PerfTest {
        public static void main(String... args) {
            int values = 110* 1000 * 1000;
            long start0 = System.nanoTime();
            long[] keysValues = generateKeys(values);

            LongIntParallelHashMultimap map = new LongIntParallelHashMultimap(222222227);
            long start = System.nanoTime();
            addKeyValues(values, keysValues, map);
            long mid = System.nanoTime();
            int sum = lookUpKeyValues(values, keysValues, map);
            long time = System.nanoTime();
            System.out.printf("Generated %.1f M keys/s, Added %.1f M/s and looked up %.1f M/s%n",
                    values * 1e3 / (start - start0), values * 1e3 / (mid - start), values * 1e3 / (time - mid));
            System.out.println("Expected " + values + " got " + sum);
        }

        private static long[] generateKeys(int values) {
            Random rand = new Random();
            long[] keysValues = new long[values];
            for (int i = 0; i < values; i++)
                keysValues[i] = rand.nextLong();
            return keysValues;
        }

        private static void addKeyValues(int values, long[] keysValues, LongIntParallelHashMultimap map) {
            for (int i = 0; i < values; i++) {
                map.put(keysValues[i], i);
            }
            assert map.size() == values;
        }

        private static int lookUpKeyValues(int values, long[] keysValues, LongIntParallelHashMultimap map) {
            int[] found = new int[8];
            int sum = 0;
            for (int i = 0; i < values; i++) {
                sum += map.get(keysValues[i], found);
            }
            return sum;
        }
    }
}

プリント

Generated 34.8 M keys/s, Added 11.1 M/s and looked up 7.6 M/s

Java 7update3を搭載した3.8GHzi7で実行します。

キャッシュにランダムにアクセスするのではなく、メインメモリにアクセスしているため、これは前のテストよりもはるかに低速です。これは本当にあなたの記憶の速度のテストです。メインメモリに対して非同期で実行できるため、書き込みは高速です。


このコレクションを使用する

final SetMultimap<Long, Integer> map = Multimaps.newSetMultimap(
        TDecorators.wrap(new TLongObjectHashMap<Collection<Integer>>()),
        new Supplier<Set<Integer>>() {
            public Set<Integer> get() {
                return TDecorators.wrap(new TIntHashSet());
            }
        });

5,000万エントリ(約16 GBを使用)で同じテストを実行すると-mx20g、次の結果が得られます。

 Generated 47.2 M keys/s, Added 0.5 M/s and looked up 0.7 M/s

1億1,000万のエントリの場合、毎秒500万の追加を実行するには、約35 GBのメモリと私のマシン(3.8 GHz)の10倍の速度のマシンが必要になります。

于 2012-04-09T07:34:05.933 に答える
0

Javaを使用する必要がある場合は、独自のハッシュテーブル/ハッシュマップを実装します。テーブルの重要なプロパティは、リンクリストを使用して衝突を処理することです。したがって、ルックアップを実行すると、リスト上のすべての要素を返すことができます。

于 2012-04-08T17:11:08.513 に答える
0

私はこの質問に答えるのに遅れているかもしれませんが、弾力性のある検索はあなたの問題を解決します。

于 2015-10-07T11:22:15.467 に答える