C/C++でトライの速度とキャッシュ効率の良い実装はありますか?私はトライが何であるかを知っていますが、車輪を再発明して、自分でそれを実装したくありません。
aNSI Cの実装を探しているなら、FreeBSDから「盗む」ことができます。探しているファイルの名前は radix.c です。カーネルでルーティングデータを管理するために使用されます。
質問は準備が整った実装に関するものでしたが、参考のために...
Judyにジャンプする前に、「 Judyとハッシュテーブルのパフォーマンス比較 」を読む必要があります。その後、タイトルをグーグルで検索することで、一生の議論と反論を読むことができます。
私が知っている明示的にキャッシュを意識したトライは、 HAT-trie です。
HAT-trieは、正しく実装されると非常にクールです。ただし、プレフィックス検索の場合、ハッシュバケットでの並べ替え手順が必要です。これは、プレフィックス構造の考え方と多少矛盾します。
やや単純なトライは burst-trie です。これは基本的に、ある種の標準ツリー(BSTなど)とトライの間を補間します。概念的には気に入っており、実装がはるかに簡単です。
libTrie で幸運に恵まれました。特にキャッシュが最適化されていない可能性がありますが、アプリケーションのパフォーマンスは常に適切です。
GCCには、「ポリシーベースのデータ構造」の一部として少数のデータ構造が付属しています。これには、いくつかのトライ実装が含まれます。
http://gcc.gnu.org/onlinedocs/libstdc++/ext/pb_ds/trie_based_containers.html
Judy配列 :ビット、整数、文字列用の非常に高速でメモリ効率の高い順序付けられたスパース動的配列。 Judy配列は、バイナリ検索ツリー(avlおよびred-black-treesを含む)よりも高速で、メモリ効率が高くなります。
参照、
C
実装を含む)http://tommyds.sourceforge.net/ でTommyDSを試すこともできます。
Ndtriesおよびjudyとの速度の比較については、サイトのベンチマークページを参照してください。
キャッシュの最適化は、おそらく64バイトのような単一のキャッシュラインにデータを収める必要があるため、おそらくしなければならないことです(ポインターなどのデータを結合し始めるとおそらく動作します) 。しかし、それはトリッキーです:-)
Burst Trie's はもう少しスペース効率が良いようです。 CPUキャッシュは非常に小さいため、どのインデックスからどの程度のキャッシュ効率が得られるかはわかりません。ただし、この種のトライは、RAM(通常のトライではできない)に大きなデータセットを保持するのに十分なほどコンパクトです。
Scalaバーストトライの実装を記述しました。これには、GWTの先行入力実装で見つかった省スペース技術も組み込まれています。
基本的なシナリオでテストしたTrieの「高速」実装を共有します。
#define ENG_LET 26
class Trie {
class TrieNode {
public:
TrieNode* sons[ENG_LET];
int strsInBranch;
bool isEndOfStr;
void print() {
cout << "----------------" << endl;
cout << "sons..";
for(int i=0; i<ENG_LET; ++i) {
if(sons[i] != NULL)
cout << " " << (char)('a'+i);
}
cout << endl;
cout << "strsInBranch = " << strsInBranch << endl;
cout << "isEndOfStr = " << isEndOfStr << endl;
for(int i=0; i<ENG_LET; ++i) {
if(sons[i] != NULL)
sons[i]->print();
}
}
TrieNode(bool _isEndOfStr = false):isEndOfStr(_isEndOfStr), strsInBranch(0) {
for(int i=0; i<ENG_LET; ++i) {
sons[i] = NULL;
}
}
~TrieNode() {
for(int i=0; i<ENG_LET; ++i) {
delete sons[i];
sons[i] = NULL;
}
}
};
TrieNode* head;
public:
Trie() { head = new TrieNode();}
~Trie() { delete head; }
void print() {
cout << "Preorder Print : " << endl;
head->print();
}
bool isExists(const string s) {
TrieNode* curr = head;
for(int i=0; i<s.size(); ++i) {
int letIdx = s[i]-'a';
if(curr->sons[letIdx] == NULL) {
return false;
}
curr = curr->sons[letIdx];
}
return curr->isEndOfStr;
}
void addString(const string& s) {
if(isExists(s))
return;
TrieNode* curr = head;
for(int i=0; i<s.size(); ++i) {
int letIdx = s[i]-'a';
if(curr->sons[letIdx] == NULL) {
curr->sons[letIdx] = new TrieNode();
}
++curr->strsInBranch;
curr = curr->sons[letIdx];
}
++curr->strsInBranch;
curr->isEndOfStr = true;
}
void removeString(const string& s) {
if(!isExists(s))
return;
TrieNode* curr = head;
for(int i=0; i<s.size(); ++i) {
int letIdx = s[i]-'a';
if(curr->sons[letIdx] == NULL) {
assert(false);
return; //string not exists, will not reach here
}
if(curr->strsInBranch==1) { //just 1 str that we want remove, remove the whole branch
delete curr;
return;
}
//more than 1 son
--curr->strsInBranch;
curr = curr->sons[letIdx];
}
curr->isEndOfStr = false;
}
void clear() {
for(int i=0; i<ENG_LET; ++i) {
delete head->sons[i];
head->sons[i] = NULL;
}
}
};
私のデータセットで言及されたいくつかのTRIE実装と比較して、marisa-trieで非常に良い結果(パフォーマンスとサイズの非常に良いバランス)がありました。