問題があります。ファイルパスプレフィックスに基づいてファイルシステムデータをスペース効率よく検索する必要があります。つまり、ソートされたテキストのプレフィックス検索。トライを使ってください、あなたが言う、そして私は同じことを考えました。問題は、他のトリックがなければ、試行はスペース効率が十分ではないということです。
私はかなりの量のデータを持っています:
- ディスク上のプレーンテキストのUnix形式のリストで約450M
- 約800万行
- gzipのデフォルトは31Mに圧縮されます
- bzip2のデフォルトは21Mに圧縮されます
記憶に残っている450M近くの場所で食べたくありません。プレフィックスの形で多くの冗長性があるので、この時点で私はおよそ1億のどこかを使用することを嬉しく思います。
このジョブにはC#を使用していますが、トライを簡単に実装するには、ファイルの行ごとに1つのリーフノードが必要です。すべてのリーフノードがテキストの最後のチャンクへの何らかの参照(32ビット、文字列の重複を最小限に抑えるための文字列データの配列へのインデックスなど)を必要とし、CLRオブジェクトのオーバーヘッドが8バイトであると仮定します(windbg / SOSを使用して確認) 、テキストストレージがまったくない状態で、構造上のオーバーヘッドに96,000,000バイト以上を費やします。
データの統計属性のいくつかを見てみましょう。トライに詰めた場合:
- 約110万のテキストの合計ユニークな「チャンク」
- テキストファイル内のディスク上の合計約16Mの一意のチャンク
- 平均チャンク長は5.5文字、最大136
- 重複を考慮しない場合、チャンクで合計約5,200万文字
- 内部トライノードの平均は約6.5の子で、最大は44です。
- 約180万の内部ノード。
葉の作成の過剰率は約15%、過剰な内部ノードの作成は22%です-過剰な作成とは、各タイプのノードの最終的な数の割合として、最終的なトライではなく、トライの構築中に作成された葉と内部ノードを意味します。
これがSOSのヒープ分析であり、最も多くのメモリが使用されている場所を示しています。
[MT ]--[Count]----[ Size]-[Class ]
03563150 11 1584 System.Collections.Hashtable+bucket[]
03561630 24 4636 System.Char[]
03563470 8 6000 System.Byte[]
00193558 425 74788 Free
00984ac8 14457 462624 MiniList`1+<GetEnumerator>d__0[[StringTrie+Node]]
03562b9c 6 11573372 System.Int32[]
*009835a0 1456066 23297056 StringTrie+InteriorNode
035576dc 1 46292000 Dictionary`2+Entry[[String],[Int32]][]
*035341d0 1456085 69730164 System.Object[]
*03560a00 1747257 80435032 System.String
*00983a54 8052746 96632952 StringTrie+LeafNode
はDictionary<string,int>
文字列チャンクをインデックスにマップするために使用されておりList<string>
、トライの構築後に破棄できますが、GCはそれを削除していないようです(このダンプの前にいくつかの明示的な収集が行われました)!gcroot
-SOSでは示されていませんルーツはありますが、後のGCで解放されると思います。
MiniList<T>
スペースの浪費を回避するためList<T>
に、正確なサイズ(つまり、線形成長、O(n^2)
追加パフォーマンス)を使用する代わりになります。T[]
これは値型であり、InteriorNode
子を追跡するために使用されます。これがパイルT[]
に追加されます。System.Object[]
したがって、「興味深い」アイテム(でマークされている*
)を追加すると、約270Mになります。これは、ディスク上の生のテキストよりも優れていますが、それでも目標に十分に近づいていません。.NETオブジェクトのオーバーヘッドが大きすぎると考え、値型の配列だけを使用してデータを格納する新しい「スリムな」トライを作成しました。
class SlimTrie
{
byte[] _stringData; // UTF8-encoded, 7-bit-encoded-length prefixed string data
// indexed by _interiorChildIndex[n].._interiorChildIndex[n]+_interiorChildCount[n]
// Indexes interior_node_index if negative (bitwise complement),
// leaf_node_group if positive.
int[] _interiorChildren;
// The interior_node_index group - all arrays use same index.
byte[] _interiorChildCount;
int[] _interiorChildIndex; // indexes _interiorChildren
int[] _interiorChunk; // indexes _stringData
// The leaf_node_index group.
int[] _leafNodes; // indexes _stringData
// ...
}
この構造により、データ量が139Mに減少しましたが、読み取り専用操作では依然として効率的にトラバース可能なトライです。そして、それはとても単純なので、私はそれをディスクに簡単に保存して復元し、毎回トライを再作成するコストを回避することができます。
それで、トライよりもプレフィックス検索のためのより効率的な構造のための提案はありますか?私が考慮すべき代替アプローチ?