10

私には特別なニーズがあり、最も重要な懸念事項は次のとおりです。

  • インメモリ
  • 非常に低いメモリフットプリント
  • 速度

これが私の「問題」です。メモリ内に、非常にまばらなビット配列を大量に格納する必要があります。これらのビットセットは「追加のみ」であり、主に交差に使用されます。巨大とは、200 000 ビット配列という意味です。

各ビットセットの範囲は [0...16 000 000] です。

取得した実際のデータを含む「のみ」10 673 ビット配列を使用して事前テストを実行したところ、次の結果が得られました。

  1% of the bit arrays (  106 bit arrays) Hamming weight: at most     1 bit  set
  5% of the bit arrays (  534 bit arrays) Hamming weight: at most     4 bits set
 10% of the bit arrays ( 1068 bit arrays) Hamming weight: at most     8 bits set
 15% of the bit arrays ( 1603 bit arrays) Hamming weight: at most    12 bits set
 20% of the bit arrays ( 2137 bit arrays) Hamming weight: at most    17 bits set
 25% of the bit arrays ( 2671 bit arrays) Hamming weight: at most    22 bits set
 30% of the bit arrays ( 3206 bit arrays) Hamming weight: at most    28 bits set
 35% of the bit arrays ( 3740 bit arrays) Hamming weight: at most    35 bits set
 40% of the bit arrays ( 4274 bit arrays) Hamming weight: at most    44 bits set
 45% of the bit arrays ( 4809 bit arrays) Hamming weight: at most    55 bits set
 50% of the bit arrays ( 5343 bit arrays) Hamming weight: at most    67 bits set
 55% of the bit arrays ( 5877 bit arrays) Hamming weight: at most    83 bits set
 60% of the bit arrays ( 6412 bit arrays) Hamming weight: at most   103 bits set
 65% of the bit arrays ( 6946 bit arrays) Hamming weight: at most   128 bits set
 70% of the bit arrays ( 7480 bit arrays) Hamming weight: at most   161 bits set
 75% of the bit arrays ( 8015 bit arrays) Hamming weight: at most   206 bits set
 80% of the bit arrays ( 8549 bit arrays) Hamming weight: at most   275 bits set
 85% of the bit arrays ( 9083 bit arrays) Hamming weight: at most   395 bits set
 90% of the bit arrays ( 9618 bit arrays) Hamming weight: at most   640 bits set
 95% of the bit arrays (10152 bit arrays) Hamming weight: at most  1453 bits set
 96% of the bit arrays (10259 bit arrays) Hamming weight: at most  1843 bits set
 97% of the bit arrays (10366 bit arrays) Hamming weight: at most  2601 bits set
 98% of the bit arrays (10473 bit arrays) Hamming weight: at most  3544 bits set
 99% of the bit arrays (10580 bit arrays) Hamming weight: at most  4992 bits set
100% of the bit arrays (10687 bit arrays) Hamming weight: at most 53153 bits set

関連する数値を見ると、明らかに圧縮されたビット配列を使用する必要がありますが、それは問題ではありません。ビット配列が「追加のみ」であることを考えると、扱いやすいままです。

オンになっているビット配列のビットは、グループ化されていますが、完全ではありません。そのため、同じ領域でいくつかのビットがオンになる傾向があります (ただし、通常は連続してオンになるわけではないため、オンになっているビットには RLE が適していません)。

私の質問は、どのような圧縮を使用するのですか?

最初のアプローチをここに置くべきか、それとも自分の質問への回答に置くべきかはわかりません。

基本的に、非常に愚かなエンコーディングを使用した「最悪のケース」のシナリオを想像しました。

  • 1 ビット: オンの場合、次の 5 ビットは「スキップ」を計算するために必要なビット数を決定します。オフの場合、最適化: 次の 5 ビットは、文字どおり (つまり、「オン」または「オフ」) に取りすぎるビット数を決定します。 '、スキップなし) [これは、他の表現よりも効率的であると判断された場合にのみ切り替えられるため、開始時には常に最適化されます (サイズに関して)]

  • 5 ビット: 次のビットがオンになる前にスキップできるビット数

  • x ビット: スキップ

例を次に示します。ビット配列には 3 ビット セットがあり、最初のビットは 3 098 137、2 番目のビットは 3 098 141、3 番目のビットは 3 098 143 です。

                               +-- now we won't skip
                               |
                               |     +-- 3 because we need 3 bits to store "6" (from 3 098 138 to 3 098 143)
                               |     |    +--- 3 098 141 is on
  22    3 098 137              |     3    | +- 3 098 143 is on
1 10110 1011110100011000011001 0 00011 000101 etc. 

最初のビットは、ビットをスキップすることを示しています。次の 5 ビット (常に 5) は、スキップするビット数を伝えるために必要なビット数を示します。 22 ビットは、3 098 137 にスキップするように指示します。1 ビットオフは、ビットをスキップしないことを伝えます。次の 5 ビット (常に 5) は、伝えます。 「そのまま」読み取るビット数 6 ビット: オフ、オフ、オフ、オン、オフ、オン 3 098 141 および 3 098 143 がオンなどを意味します。

これらのビット配列の驚くべきスパース性を見ると、これは非常にサイズ効率が良いようです。

そのため、そのエンコーディングを使用して、サンプル データを取得し、「最悪の場合」のシナリオを計算しました (まだアルゴリズムを書いていないので、最初にここからいくつかの入力を取得したいと思います)。また、5 ビットが常に最大値 (24 ビット) に設定されますが、これはもちろん起こりません。

私は、「最悪の最悪」のケースが何であるかについて、非常に大まかな概算を得るためにそれを行いました.

とても嬉しい驚きでした:

Worst case scenario: 

108 913 290 bits needed for the 10 687 very sparse bit arrays
12.9 MB (13 295 KB)

データは実際のデータであり、すべてのデータが類似しているため、さらに悪いことに、200,000 ビットの配列を約 240 MB に格納できることがわかっています。これは問題ありません。

実際のエンコーディングはそれよりもはるかに少ないと確信していますが、まだ実際に書いていないので、(非常に簡単に)「最悪のケース」しか計算できないため、そのケースのみを示します。

これをよりサイズ効率的にする方法に関するヒント/アイデア(これらは非常にまばらなビット配列であり、数十万個あり、メモリ内にある必要があり、「追加のみ」になることを覚えておいてください) ?

「追加のみ」のケースについて

基本的に、私は1つの成長する「広がり」(範囲ですが、「広がり」は私が理解している実際の用語です)と、いくつかのビットセットを持つ多くのビット配列を持っています。範囲がたとえば 0 から 1 000 000 になると、すべてのビット配列は 0 から 1 000 000 になります。範囲が 1 000 001 まで大きくなると、すべてのビット配列も 1 ビットずつ大きくなります。ただし、これらのビット配列のほとんどは末尾に「0」が追加され、ビット配列の約 4 ~ 8 には末尾に「1」が追加されます。ただし、どのビット配列に 0 または 1 が追加されるかを事前に予測することはできません。

したがって、すべて同じサイズで、すべて非常にまばらで (ビットセットの < 0.5%)、範囲の拡大に伴ってすべて「成長」しているビット配列がたくさんあります (したがって、それらはすべて常に成長しています)。同じレートで)。


ジュディの配列は素晴らしいです。しかし、私は数年前にそれらについて読みましたが、そのことは「私の頭の上」にありました。Judy 配列は C のみの 20KLOC ライブラリであり、私はそれを再実装するつもりはありません。しかし、彼らは素晴らしいです。

したがって、これらすべてを比較的単純なままにしておきたいと思いますが、これは、非常にまばらなビット配列の特別な「追加のみ」のプロパティを見てそれほど大げさではありません。

4

6 に答える 6

4

どのプログラミング言語を使いたいか、あなたは言いませんでした。Judy は「C のみ」であるため不要のようです... C# を使用している場合は、代わりに私のCompact Patricia Trieを使用できます。ほぼ 4500 LOC (コメント済み) で、Judy と同様のアイデアを使用していますが、.NET の制限により、各トライのサイズと速度は理想的ではありません。交差点の計算にも最適化されていませんが、そのようなアルゴリズムを追加できます。CP Tries に関する記事ではこの点は強調されていませんが、セット (スパース ビット配列) は辞書よりもはるかにコンパクトに格納できます (記事のグラフは、セットではなく辞書のサイズと速度を示しています)。

最良のケースは、ビットの密なクラスターです。占有率が 50% (1 つおきのビット セット) の場合、キーごとに必要なビット数は 8 ビット未満です (整数ごとに 4 ビット未満)。(訂正: 8 ビット未満、それ以上ではありません。)

データのおおよその表現のみが必要な場合は、ブルーム フィルターを使用します。

ところで、「追加のみ」とはどういう意味ですか? キーを追加するだけですか、それとも追加する各キーが以前に追加したキーよりも大きいということですか?

更新:より大きなキーを追加するだけなので、場合によっては特別なアルゴリズムを設計する必要があります。IMO、カスタム アルゴリズムを設計するときは、できるだけ単純にする必要があります。したがって、異なるビットセットのキーが相関していないと仮定する私の考えは次のとおりです(したがって、異なるビットセット間でデータを圧縮しようとする利点はありません):

ビットセットは、32 ビット スロットの並べ替えられた配列で表されます。ソートされているため、バイナリ検索を使用してキーを見つけることができます。各スロットは、24 ビットの「プレフィックス」と 8 ビットの「フラグ」で構成されます。各スロットは、8 つのキーの領域を表します。「フラグ」は、リージョン内の 8 つのキーのどれがビットセットに存在するかを示し、「プレフィックス」は、キーのビット 3 から 26 を指定することによって、どのリージョンについて話しているかを示します。たとえば、ビットセットで次のビットが「1」の場合:

1, 3, 4, 1094, 8001, 8002, 8007, 8009

...次に、ビットセットは 4 つのスロット (16 バイト) の配列で表されます。

Prefix:     0,  136, 1000, 1001
 Flags:  0x15, 0x40, 0x86, 0x02

最初のスロットは 1、3、4 を表します (ビット 1、3、および 4 が数値 0x15 に設定されていることに注意してください)。2 番目のスロットは 1094 (136 * 8 + 6) を表します。3 番目のスロットは 8001、8002、および 8007 を表します。4 番目のスロットは 8009 を表します。これは理にかなっていますか?

これがあなたのアイデアと同じくらいコンパクトかどうかはわかりません。しかし、より高速なクエリと高速な変更が得られると思います。また、実装もかなり簡単になります。

于 2010-11-02T16:18:47.353 に答える
3

ビット配列には二分木を使用できます。たとえば、[M..N] の範囲の配列があるとします。次のように保管してください。

フィボナッチ、ゴロム、またはライス コードなど、[0...RAM サイズ] の数値エンコーディングを選択します (実際のデータでプログラムをプロファイリングした後、最も適切な表現を選択できます)。

  1. 配列が空の場合 (ビットが設定されていない場合)、番号 0 として格納します。
  2. 配列がいっぱいの場合 (すべてのビットが設定されている場合)、番号 1 として格納します。
  3. それ以外の場合は、[M..(M+N)/2-1] の A と [(M+N)/2..N] の B の 2 つの部分に分割します。
  4. このアルゴリズムを再帰的に使用して、P0 と P1 の表現を生成します。
  5. P0 の長さ (ビット単位または他の単位の長さは整数) を取得し、数値として格納します (長さが 1 の場合は 1 を加算する必要がある場合があります。たとえば、0 を単一のビット 0 として格納します)。
  6. P0 を保存してから P1 を保存します。

この場合、制限が一般的である場合、交差と結合の操作は自明な再帰です。

交差点:

  1. 配列 A が空の場合、0 を格納します。
  2. 配列 A がいっぱいの場合、B のコピーを保存します
  3. それ以外の場合は、配列を分割し、両方の半分の交差を作成し、最初の半分の長さを格納し、次に両方の半分を格納します。

このアルゴリズムは、ビット (最もコンパクトにする必要がある場合) とバイト/ワード (ビット操作が非常に遅い場合) を処理できます。

また、再帰のレベルを下げるために、サイズが制限 (たとえば 8 要素) 未満のすべての配列で、単一ビット セットの配列に特別なエンコーディングを追加することもできます。

欠点は、いくつかのハックがないと、配列への要素の追加/配列からの要素の削除が複雑な操作になることです (交差/結合操作と同じくらい複雑です)。

たとえば、単一の 0xAB ビットが設定された配列は、次のように 0..0xFF の配列に格納する必要があります (疑似コード):

0  1      2   3  4      5  6  7      8  9  10     11 12     13    14     15     16
1, EMPTY, 13, 1, EMPTY, 9, 1, EMPTY, 5, 1, EMPTY, 1, EMPTY, FULL, EMPTY, EMPTY, EMPTY
                                                   |  AA  | AB  |
                                         |A8..A9| AA    ..   AB |
                                      | A8         ..        AB |AC..AF|
                            |A0..A7| A8             ..              AF |
                         | A0                 ..                    AF |B0..BF|
               |80..9F| A0                        ..                       BF |
            | 80                                 ..                        BF |C0..FF|
 | 0..7F| 80                                          ..                          FF |       

EMPTY と FULL は、空の配列と完全な配列のコードであり、数値は要素の長さです (バイト、ビットなどの実際の長さに置き換える必要があります)。

高速なシングル ビット チェックが必要ない場合は、最も単純なアプローチを使用できます。コードを使用してセット ビット間の距離を保存するだけです: フィボナッチ、ライス、ゴロム、レーベンシュタイン、エリアスなど。または別のものを発明します。最小のコード長を取得するには、-log p/log 2 にできるだけ近いコード長のコードを使用する必要があることに注意してください。ここで、p はそのコードの確率です。そのためにハフマンコードを使用できます。

たとえば、エリアス ガンマ コードを使用すると、配列は次のようになります。

0 1 0000 1 1 000 1 0 1 000000000000000000 1 000000000000000000 
2     5   1   4    2         19                   18           (distance)

次のようにエンコードする必要があります。

010 00101 1 00100 010 000010011 000010010
 2    5   1   4    2     19        18     (distance code explained)

また、均一なビット分布を持つ配列の場合、ほとんどコンパクトなのは算術エンコーディングですが、非常に CPU 時間がかかります。そのような配列をビットごとに読み書きする必要があり、高速スキップを利用できないためです。

于 2010-11-02T15:35:20.300 に答える
3

圧縮されたビットマップを調べることができます。一般的な戦略は、ワード アラインされたランレングス エンコーディングを使用することです。

C++ 実装:

https://github.com/lemire/EWAHBoolArray

Java 実装:

https://github.com/lemire/javaewah

参照:

Daniel Lemire、Owen Kaser、Kamel Aouiche、並べ替えにより、単語に位置合わせされたビットマップ インデックスが改善されます。データ & ナレッジ エンジニアリング 69 (1)、3 ~ 28 ページ、2010 年。 http://arxiv.org/abs/0901.3751

于 2010-11-04T12:44:49.010 に答える
2

それらが探しているものと正確に一致しない場合でも、Judy treesをチェックする価値があります。Judy は順序付きマップ用に大幅に最適化されたライブラリであり、1 つの構成はマップではなくビットセットとして特別に設計されています。ただし、交差はネイティブに最適化された操作の1つではないと思います...

一般的な考え方は、レベルごとに固定数のアドレス ビットを持つツリーを使用し、各レベルで疎性を利用することです。これにより、最悪の場合でも非常に優れた圧縮が得られ、クエリのパフォーマンスも高速になります。交差操作は比較的簡単で、非常に高速になる可能性があると思います。

いずれにせよ、最高のものから盗むことは常に良い考えです!

于 2010-11-01T23:52:25.977 に答える
1

二分決定図 (BDD)、より正確にはゼロ抑制二分決定図 (ZBDD) に興味があるかもしれません。

これらは、圧縮された方法でセットを表すために使用されます。他の圧縮フォームとは異なり、操作 (交差の設定や要素の挿入など、「追加のみ」の操作ですか?) は圧縮フォームで直接実行されます。

于 2010-11-02T16:58:47.903 に答える
1

とにかくたくさんの交差テストを行うことを考えると、すべてのビットベクトルを並行して格納してみる必要があるかもしれません。まばらな 16M エントリ リストが 1 つ。そのリストの各エントリには、200k の入力ビットベクトルのうち、その位置に「1」があるリストが含まれています。入力ベクトルごとに約 5 ビットしか設定されていないか、合計 1M エントリしかないと予想しているように見えますか? トップレベルとバケットのストローマン リンク リストの実装と、交差がまったくない最悪のケース (つまり、それぞれ 1 つの要素を持つ 1M のバケット) を使用すると、すべてを 32MB に格納できます。

于 2010-11-02T00:19:34.110 に答える