トライ(ダブル配列,簡潔データ構造)と STL コンテナの比較(最新版)
[2011/11/30 更新; std::(unordered_)map でメモリ使用量を見積もる - ny23の日記に従い,STL コンテナのメモリ使用量を計測]
[2011/02/21 更新: marisa-trie 0.1.3; 発表資料(Prefix/Patricia Trie の入れ子による辞書圧縮) - やた@はてな日記 にてこの記事の実験結果を引用されているので,以後原則更新しないこととする.なお,marisa-trie は 検索時間が短くなりました - やた@はてな日記 にあるように,marisa-0.2.0-beta3 以降ではさらに検索が速くなっています.]
[2011/02/18 更新: marisa-trie の仕様変更に伴い,追記の記述を整合性が取れるよう変更; 最新版では未確認]
id:s-yata さんが marisa-trie を公開されたので,例によってベンチマークを取った結果を公開しておきます.以前,ux-trie のバグ絡みでコードを読んだとき,TAIL 部でトライの段数をもっと増やすとどうなるのかなと思っていたところだったので,非常に良いタイミングでした.marisa-trie でトライを構築する際のパラメタは,トライの数(build の第三引数)のところしか変えていません.Build は構築時間,Search は辞書順,Search* はランダム順の検索時間.Wikipedia-en は 20090929 のもの.
個々のライブラリへのリンクや細かい表記の意味,marisa-trie 以外の結果に関する考察は以下の記事を参照.
個人的には marisa-trie は圧縮率の高さよりも,その検索速度の速さにより魅力を感じます.
[追記] marisa-trie で patricia trie -> prefix tree にすると,Wikipedia-en ではトライのサイズが1-2割増え,検索速度も1-2割落ちます(郵便番号辞書の方では,トライのサイズはほぼ同じで検索速度は prefix tree の方が逆に僅かに高速).次に,TAIL 部を文字列化しないでそのままトライで保持すると,(Wikipedia-en では)トライの数が一つの時のみサイズが大幅に増え(tx と同じぐらい),検索速度も特にランダム検索順で大きく(1/2 ぐらいまで)低下します.この辺り,ほぼ予想通り*1の挙動です.
====================================================================== Wikipedia-en 記事タイトル | Build | Search | Search* | Size [bytes] #keys=6,890,514 | | | | 136,297,337 ====================================================================== sorted keys Darts 0.32 | 14.283s | 0.527s | 6.694s | 552,547,920 darts-clone 0.32g | 6.575s | 0.625s | 5.946s | 258,735,104 darts-clone 0.32g v:1 | 15.917s | 0.764s | 6.227s | 95,080,448 *darts-clone 0.32e5 | 14.532s | 0.924s | 5.890s | 113,360,140 *darts-clone 0.32e5 v:1 | 13.996s | 0.852s | 5.562s | 87,122,880 darts-clone 0.32e5 huge| 4.128s | 0.554s | 5.920s | 147,491,712 *DASTrie 1.0 -c | NA | NA | NA | NA DASTrie 1.0 | 4.951s | 1.012s | 6.212s | 157,593,742 *doar 0.0.13 static | 8.684s | 0.672s | 5.664s | 123,000,249 -------------------------------------------------------------------- *doar 0.0.13 dynamic | +3.964s | 0.670s | 5.757s | +123,000,249 dda | 3.222s | 0.612s | 7.191s | 517,091,328 --------------------------------------------------------------------- map k:char* | 2.905s | 2.122s | 17.352s | ~515,663,184 map k:string | 10.482s | 6.041s | 25.479s | >515,663,184 unordered_map k:char* | 6.271s | 2.013s | 3.638s | ~460,539,072 unordered_map k:string | 7.303s | 2.182s | 4.540s | >460,539,072 -------------------------------------------------------------------- marisa 0.1.3 #tries=1 | 23.118s | 4.087s | 16.491s | 49,350,092 marisa 0.1.3 #tries=2 | 25.411s | 6.921s | 21.707s | 37,743,122 marisa 0.1.3 #tries=3 | 25.786s | 7.524s | 22.945s | 35,767,947 marisa 0.1.3 #tries=10| 26.206s | 7.841s | 23.633s | 34,802,056 tx 0.18 | 18.611s | 24.969s | 62.604s | 87,520,717 ux 0.1.2 | 46.515s | 45.921s | 67.670s | 49,134,003 ux 0.1.2 isTailUX=false| 16.744s | 30.197s | 49.810s | 105,423,225 ===================================================================== randomized keys doar 0.0.13 dynamic |+24.902s | 1.952s | 5.645s | +123,000,249 dda | 10.017s | 3.647s | 8.589s | 517,091,328 -------------------------------------------------------------------- map k:char* | 18.576s | 3.637s | 21.164s | ~515,663,184 map k:string | 27.897s | 8.417s | 30.713s | >515,663,184 unordered_map k:char* | 6.360s | 3.754s | 3.646s | ~460,539,072 unordered_map k:string | 7.302s | 4.605s | 4.549s | >460,539,072 -------------------------------------------------------------------- marisa 0.1.3 #tries=1 | 32.635s | 5.381s | 16.232s | 49,350,092 marisa 0.1.3 #tries=2 | 34.408s | 8.400s | 21.977s | 37,743,122 marisa 0.1.3 #tries=3 | 34.884s | 9.232s | 22.945s | 35,767,947 marisa 0.1.3 #tries=10| 35.405s | 9.556s | 23.660s | 34,802,056 tx 0.18 | 38.718s | 26.434s | 62.358s | 87,520,717 ux 0.1.2 | 63.228s | 47.239s | 68.075s | 49,134,003 ux 0.1.2 isTailUX=false| 33.159s | 31.547s | 50.461s | 105,423,225 ====================================================================== ====================================================================== 郵便番号辞書 | Build | Search | Search* | Size [bytes] #keys=118,821 | | | | 950,568 ====================================================================== sorted keys Darts 0.32 | 0.039s | 0.003s | 0.012s | 2,156,576 darts-clone 0.32g | 0.038s | 0.003s | 0.010s | 1,069,056 darts-clone 0.32g v:1 | 0.020s | 0.003s | 0.007s | 106,496 *darts-clone 0.32e5 | 0.171s | 0.004s | 0.013s | 1,154,676 *darts-clone 0.32e5 v:1 | 0.165s | 0.004s | 0.011s | 650,116 darts-clone 0.32e5 huge| 0.141s | 0.004s | 0.013s | 1,363,656 *DASTrie 1.0 -c | 2.226s | 0.010s | 0.018s | 1,248,609 DASTrie 1.0 | 2.354s | 0.010s | 0.019s | 1,411,783 *doar 0.0.13 static | 0.064s | 0.004s | 0.013s | 1,204,561 -------------------------------------------------------------------- *doar 0.0.13 dynamic | +0.091s | 0.004s | 0.013s | +1,204,561 dda | 0.023s | 0.004s | 0.014s | 2,129,920 -------------------------------------------------------------------- map k:char* | 0.043s | 0.028s | 0.074s | ~7,604,544 map k:string | 0.137s | 0.072s | 0.147s | >7,604,544 unordered_map k:char* | 0.023s | 0.010s | 0.027s | ~6,653,976 unordered_map k:string | 0.039s | 0.013s | 0.038s | >6,653,976 -------------------------------------------------------------------- marisa 0.1.3 #tries=1 | 0.083s | 0.033s | 0.059s | 232,729 marisa 0.1.3 #tries=2 | 0.083s | 0.034s | 0.060s | 232,111 marisa 0.1.3 #tries=3 | 0.083s | 0.034s | 0.060s | 232,270 marisa 0.1.3 #tries=10| 0.083s | 0.034s | 0.060s | 233,404 tx 0.18 | 0.096s | 0.090s | 0.129s | 222,888 ux 0.1.2 | 0.092s | 0.158s | 0.187s | 220,209 ux 0.1.2 isTailUX=false| 0.092s | 0.157s | 0.187s | 222,165 ===================================================================== randomized keys *doar 0.0.13 dynamic | +0.104s | 0.009s | 0.013s | +1,204,561 dda | 0.036s | 0.010s | 0.013s | 2,129,920 -------------------------------------------------------------------- map k:char* | 0.068s | 0.035s | 0.085s | ~7,604,544 map k:string | 0.143s | 0.092s | 0.168s | >7,604,544 unordered_map k:char* | 0.029s | 0.023s | 0.027s | ~6,653,976 unordered_map k:string | 0.049s | 0.036s | 0.039s | >6,653,976 -------------------------------------------------------------------- marisa 0.1.3 #tries=1 | 0.121s | 0.036s | 0.059s | 232,729 marisa 0.1.3 #tries=2 | 0.121s | 0.037s | 0.060s | 232,111 marisa 0.1.3 #tries=3 | 0.121s | 0.037s | 0.060s | 232,270 marisa 0.1.3 #tries=10| 0.121s | 0.037s | 0.060s | 233,404 tx 0.18 | 0.168s | 0.097s | 0.129s | 222,888 ux 0.1.2 | 0.174s | 0.165s | 0.187s | 220,209 ux 0.1.2 isTailUX=false| 0.174s | 0.165s | 0.187s | 222,165 ===================================================================== (compiled with gcc 4.6.0 20110212, -O2 -march=core2 -m64)
*1:トライの数を一つにして,TAIL 部の処理を(速度の速い)文字列の照合のみで済ませる場合に検索速度は最速となり,TAIL 部のトライの数を増やせば増やすほど,TAIL で文字列照合する部分が減っていくので,TAIL なしの単純なトライの検索速度に近づく.