Succinct Vector, Wavelet Matrix
       implementation


         2013/2/20 社内勉強会
      3/21 update version of marisa
                光成滋生
今回の実装(中のもの)
Succinct vector
  https://github.com/herumi/cybozulib/blob/m
   aster/include/cybozu/sucvector.hpp
Wavelet matrix
  https://github.com/herumi/cybozulib/blob/m
   aster/include/cybozu/wavelet_matrix.hpp
Benchmark code
  https://github.com/herumi/opti/
    rank_test.cpp, wm_test.cpp




                                           2 / 16
SucVector
v={0, 1}からなる長さNのビットベクトル
 高速なrankのために二つのテーブルL, Sを用意
 Lは大雑把なrankのためのテーブル
 Sはそこからの差分を求めるための補助テーブル
LとSの具体的なサイズや値は?




                              3 / 16
step 0
Lは256bitごとの累積を格納するテーブル
 SはLからの差分として64bitごとの累積和
   Lはuint32_t
   Sはuint8_tで格納
   必要量はbitあたり(32 + 8 * 4) / 256 = 1 / 4
 Lの値がuint32_t → Nの上限が0xffffffff
 Lの値をuint64_tで格納?
   ほとんど"すかすか"だが…
   必要量はbitあたり(64 + 8 * 4) / 256 = 3 / 8




                                           4 / 16
step 1
データをインタリーブする
オリジナルのv, L, Sがばらばらにあると
 それぞれのアクセス時にキャッシュミス発生
rank(i)の計算に必要なi番目のv, L, Sをセット
 256bitのv, 32bitのL, 8bit x 4のS

Block {
    uint64_t org[4];
    uint32_t L;
    uint8_t S[4];
};


                                  5 / 16
step 2
N < 2^32の制限を外したい
 uint32_t L → uint64_t Lへ変更
 Blockがuint64_tの倍数でなくなりpadding発生
 もったいないのでuint8_t S[8];にする
 つまり256個ずつではなく512個ずつに
 するとSの累積和が256を超えるのでuint8_tに入
  らない → 累積和をあきらめる
    必要量はbitあたり(64 + 8 * 8) / 512 = 1 / 4
Block {
    uint64_t org[8];
    uint64_t L;
    uint8_t S[8];
};                                          6 / 16
step 3
uint8_t S[8];の部分和を高速に求める
 SIMD命令のpsadbwを使ったテクニック
   http://homepage1.nifty.com/herumi/diary/1206.h
    tml#25
   2012年度夏のプログラム・シンポジウム
    http://spro2012.prosym.jp/
   http://www.slideshare.net/herumi/prosym2012/




                                               7 / 16
step 4
step 0のuint8_t S[4];を見直す
 S[i] = [0, i * 64)のビットの和
 S[0]は常に0
 この領域を使ってみる
   2^32 * 256 = 2^40までOK
      オリジナルのビットベクトルだけで128GiB
      現時点では殆ど制限にならないだろう
 必要量はbitあたり1/4で今までと変わらない
 ブロックの単位が512bitから256bit
   キャッシュミスがへる
 累積和テーブルOK
   psadbwのせこい最適化不要(ちょっと残念…)
                                8 / 16
rankのベンチマーク
Xeon 5650 + 96GiB + gcc 4.6.3
N=2^31のランダムなビットベクトルのrank
 時間(clk)
 SucVec : 私の実装(cybozu/sucvector.hpp)
 marisa : marisa-0.2.2(括弧内は0.2.0)
 sux : sux-0.7
 sdsl : sdsl (2012/9/5時点のsnapshot)
 shell : shellinford-r27
 wat : wat_array-0.0.6
    SucVec   marisa           sux      sdsl     shell    wat
    84.22    152.95(162.77)   143.23   103.73   385.82   312.51

                                                                  9 / 16
selectの実装
テーブルLを使って大雑把な2分探索
 レンジが256bitになったら64bit単位で探索
 64bit(8bit x 8)をpopcntを使って8bitまで狭める
 最後の8bitはテーブル引き
2分探索の初期値(0, L.size())を狭める
 selTbl[i] = select(1024 * i)をテーブルに持つ
   posUnit = 1024は適当
   必要なメモリはbitあたり1 / 1024 * 32 = 1 / 32
ベンチマーク(rankと同じ条件)
   SucVec   marisa           sux      sdsl   shell     wat
   608.45   622.96(659.30)   891.47   --     1816.38   2205.60

                                                                 10 / 16
Wavelet行列の実装
rank()の中で各ビットベクトルの呼び出し回数
 を半減(@echizen_tmさんのアイデア)
 http://ja.scribd.com/doc/102636443/Wavel
  et-Matrix
 必要なテーブルは文字の種類 x 64bit




                                       11 / 16
how to create table for rank
rankの高速化のためのテーブル生成は再帰を
 使えば比較的簡潔にかける
 rankLtにも同様のテクニックが使える
void initFromTbl(std::vector<size_t>& tbl,
 size_t pos, size_t from, size_t i) {
  if (i == valBitLen_) {
    tbl[pos] = from;
  } else {
    initFromTbl(tbl,pos,
           svv[i].rank(false, from), i + 1);
    initFromTbl(tbl,pos+(size_t(1)<<(valBitLen_-1-i)),
           svv[i].rank(true, from) + offTbl[i], i + 1);
    }
}
                                                    12 / 16
selectのためのテーブル引き
SucVectorと同じやりかた
 必要なテーブルサイズは文字種類(=C)倍?
  256個だと256倍?
 トータルの個数がNとするとそれぞれのサイズの平
  均はN/Cになるので(N/C) * C = Nとなる
 ただし値が小さいのでposUnit = 1024ではなく
  256とかにしたほうがよさそう
  N=2^32のときN/256*4(32bit制限あり)=64MiB
  manabeさんのwavelet_matrix::selectはもっとよい方
   法をとっている?



                                      13 / 16
how to create table for select
各文字vに対してselTbl[i] = select(256 * i);を
 求めるとNが大きいときすさまじく遅かった
 入力ベクトルをC回なめることになる
 使い物にならない
入力ベクトルを一度なめるだけで各Nに対して
 まとめてselTbl[i]を作るように修正




                                   14 / 16
ベンチマーク
Xeon 5650 + 96GiB + gcc 4.6.3
   cyWav : 私の実装(cybozu/wavelet_matrix.hpp)
   shell : shellinford-r27
   wave : wavelet-matrix-cpp(2013/2/4時点)
   wat : wat_array-0.06(Wavelet Tree)
2^26個の8bitデータに対する操作にかかった時間(usec)                2^30個の8bitデータ
          cyWav     shell     wave     wat       cyWav     wave
 get         0.59      1.03     0.90     1.45       0.74     1.53
 rank        0.60      1.18     0.95     1.52       0.73     1.75
 rankLT      0.60      1.47     0.97     1.52       0.79     1.78
 select      3.20     73.20     3.60     5.90       3.65     9.35


                                                              15 / 16
参考文献
echizen_tmさん
 ウェーブレット行列最速攻略〜予告編〜
   http://ja.scribd.com/doc/102636443/Wavelet-
    Matrix
hiroshi-manabeさん
 https://github.com/hiroshi-manabe/wavelet-
  matrix-cpp/
岡野原さん
 http://code.google.com/p/wat-array/
その他いろいろ

                                             16 / 16

Wavelet matrix implementation

  • 1.
    Succinct Vector, WaveletMatrix implementation 2013/2/20 社内勉強会 3/21 update version of marisa 光成滋生
  • 2.
    今回の実装(中のもの) Succinct vector https://github.com/herumi/cybozulib/blob/m aster/include/cybozu/sucvector.hpp Wavelet matrix https://github.com/herumi/cybozulib/blob/m aster/include/cybozu/wavelet_matrix.hpp Benchmark code https://github.com/herumi/opti/ rank_test.cpp, wm_test.cpp 2 / 16
  • 3.
    SucVector v={0, 1}からなる長さNのビットベクトル 高速なrankのために二つのテーブルL,Sを用意 Lは大雑把なrankのためのテーブル Sはそこからの差分を求めるための補助テーブル LとSの具体的なサイズや値は? 3 / 16
  • 4.
    step 0 Lは256bitごとの累積を格納するテーブル SはLからの差分として64bitごとの累積和 Lはuint32_t Sはuint8_tで格納 必要量はbitあたり(32 + 8 * 4) / 256 = 1 / 4 Lの値がuint32_t → Nの上限が0xffffffff Lの値をuint64_tで格納? ほとんど"すかすか"だが… 必要量はbitあたり(64 + 8 * 4) / 256 = 3 / 8 4 / 16
  • 5.
    step 1 データをインタリーブする オリジナルのv, L,Sがばらばらにあると それぞれのアクセス時にキャッシュミス発生 rank(i)の計算に必要なi番目のv, L, Sをセット 256bitのv, 32bitのL, 8bit x 4のS Block { uint64_t org[4]; uint32_t L; uint8_t S[4]; }; 5 / 16
  • 6.
    step 2 N <2^32の制限を外したい uint32_t L → uint64_t Lへ変更 Blockがuint64_tの倍数でなくなりpadding発生 もったいないのでuint8_t S[8];にする つまり256個ずつではなく512個ずつに するとSの累積和が256を超えるのでuint8_tに入 らない → 累積和をあきらめる 必要量はbitあたり(64 + 8 * 8) / 512 = 1 / 4 Block { uint64_t org[8]; uint64_t L; uint8_t S[8]; }; 6 / 16
  • 7.
    step 3 uint8_t S[8];の部分和を高速に求める SIMD命令のpsadbwを使ったテクニック http://homepage1.nifty.com/herumi/diary/1206.h tml#25 2012年度夏のプログラム・シンポジウム http://spro2012.prosym.jp/ http://www.slideshare.net/herumi/prosym2012/ 7 / 16
  • 8.
    step 4 step 0のuint8_tS[4];を見直す S[i] = [0, i * 64)のビットの和 S[0]は常に0 この領域を使ってみる 2^32 * 256 = 2^40までOK  オリジナルのビットベクトルだけで128GiB  現時点では殆ど制限にならないだろう 必要量はbitあたり1/4で今までと変わらない ブロックの単位が512bitから256bit キャッシュミスがへる 累積和テーブルOK psadbwのせこい最適化不要(ちょっと残念…) 8 / 16
  • 9.
    rankのベンチマーク Xeon 5650 +96GiB + gcc 4.6.3 N=2^31のランダムなビットベクトルのrank 時間(clk) SucVec : 私の実装(cybozu/sucvector.hpp) marisa : marisa-0.2.2(括弧内は0.2.0) sux : sux-0.7 sdsl : sdsl (2012/9/5時点のsnapshot) shell : shellinford-r27 wat : wat_array-0.0.6 SucVec marisa sux sdsl shell wat 84.22 152.95(162.77) 143.23 103.73 385.82 312.51 9 / 16
  • 10.
    selectの実装 テーブルLを使って大雑把な2分探索 レンジが256bitになったら64bit単位で探索 64bit(8bitx 8)をpopcntを使って8bitまで狭める 最後の8bitはテーブル引き 2分探索の初期値(0, L.size())を狭める selTbl[i] = select(1024 * i)をテーブルに持つ posUnit = 1024は適当 必要なメモリはbitあたり1 / 1024 * 32 = 1 / 32 ベンチマーク(rankと同じ条件) SucVec marisa sux sdsl shell wat 608.45 622.96(659.30) 891.47 -- 1816.38 2205.60 10 / 16
  • 11.
  • 12.
    how to createtable for rank rankの高速化のためのテーブル生成は再帰を 使えば比較的簡潔にかける rankLtにも同様のテクニックが使える void initFromTbl(std::vector<size_t>& tbl, size_t pos, size_t from, size_t i) { if (i == valBitLen_) { tbl[pos] = from; } else { initFromTbl(tbl,pos, svv[i].rank(false, from), i + 1); initFromTbl(tbl,pos+(size_t(1)<<(valBitLen_-1-i)), svv[i].rank(true, from) + offTbl[i], i + 1); } } 12 / 16
  • 13.
    selectのためのテーブル引き SucVectorと同じやりかた 必要なテーブルサイズは文字種類(=C)倍? 256個だと256倍? トータルの個数がNとするとそれぞれのサイズの平 均はN/Cになるので(N/C) * C = Nとなる ただし値が小さいのでposUnit = 1024ではなく 256とかにしたほうがよさそう N=2^32のときN/256*4(32bit制限あり)=64MiB manabeさんのwavelet_matrix::selectはもっとよい方 法をとっている? 13 / 16
  • 14.
    how to createtable for select 各文字vに対してselTbl[i] = select(256 * i);を 求めるとNが大きいときすさまじく遅かった 入力ベクトルをC回なめることになる 使い物にならない 入力ベクトルを一度なめるだけで各Nに対して まとめてselTbl[i]を作るように修正 14 / 16
  • 15.
    ベンチマーク Xeon 5650 +96GiB + gcc 4.6.3 cyWav : 私の実装(cybozu/wavelet_matrix.hpp) shell : shellinford-r27 wave : wavelet-matrix-cpp(2013/2/4時点) wat : wat_array-0.06(Wavelet Tree) 2^26個の8bitデータに対する操作にかかった時間(usec) 2^30個の8bitデータ cyWav shell wave wat cyWav wave get 0.59 1.03 0.90 1.45 0.74 1.53 rank 0.60 1.18 0.95 1.52 0.73 1.75 rankLT 0.60 1.47 0.97 1.52 0.79 1.78 select 3.20 73.20 3.60 5.90 3.65 9.35 15 / 16
  • 16.
    参考文献 echizen_tmさん ウェーブレット行列最速攻略〜予告編〜 http://ja.scribd.com/doc/102636443/Wavelet- Matrix hiroshi-manabeさん https://github.com/hiroshi-manabe/wavelet- matrix-cpp/ 岡野原さん http://code.google.com/p/wat-array/ その他いろいろ 16 / 16