よくわかるHopscotch Hashing
@kumagi
Locking!!!!
このアルゴリズムはLock-freeではありません!
Lock-freeを期待して見にきた方は回れ右!
Hashmapとは?• みんな大好き連想配列!
– Rubyでも Perlでも Pythonでも人気者!– C++でなら unordered_map、 Javaなら HashMap
• 何かの値をキーとして別の値を保存・検索できるデータ構造– 「山!」→「川!」
• とにかく高速!– O(1)で動作するデータ構造
• 大量のデータを入れても検索速度が変わらないただ一つのデータ構造
その仕組み
Hash関数
保存
山!
川!
8番目!
Hash関数に通すだけで保存 or検索すべき場所が判明する!
欠点
混んでくるとぶつかる
ぶつかったら?
大きなところへ引っ越せばいい
広々!
でもそれは最後の手段• Hash値が衝突するのはそんなに珍しくない
– そのために配列確保しなおしてたら遅すぎる– メモリ効率悪すぎる
• メモリにやさしい解決方法が望まれている
その戦略• 大きく分けて二つ
Open Addressing
Closed Addressing
or
Closed Addressing
新しく作ってポインタで繋ぐ!
ぶつかっても
Closed Addressing• 1つのアドレスにつき 1つの場所 (とそこからポインタで繋がった場所 )にしか対象の物が置かれないから Closedと呼ばれる
• ポインタで繋がるリスト構造から「チェインハッシュ」とも呼ばれる
• 利点– チェインの長さにだけ気をつければ O(1)の速度を保てる– ポインタを繋ぎ換えれば LinearHashなどが作れるし簡単
• 欠点– ポインタを辿るのが遅い
対するOpen Addressingは…
Open Addressing
ぶつかっても
隣を使う!
やったね!
選べる探索バリエーション (併用不可 )
+1 +1
±1, 2, 4, 8, 16…
±Hash(x)
Liner Probing
Quadratic Probing
Double Hashing
Open Addressing• 一つの Hash値に対して配列上の複数の場所が該当しうるので Openと名がつくんだと思う
• 利点– キャッシュの局所性を生かせるので速い(特に Linear Probing)– ポインタの分だけ省メモリ
• 欠点– 削除時にはデータを消さずに削除フラグを立てて再利用させるだけ• データが存在している事そのものが「まだ配列の続きにあるかもよ」という状態を表しているので完全に消しちゃうとマズい
• よって挿入・削除で密度が上がってくると入ってるデータが少ない時でも検索・挿入・削除の性能がガタ落ちする
ベンチマーク比較
使用済み要素の割合
キャッシュミス頻度
Cuckoo Hashing
• 鳥のカッコウの習性のように、托卵する際に他の鳥の卵があった場合に退けるハッシュマップ– OpenAddressingにも ClosedAddressingにも分類しがたい
• 2つの配列と 2つのハッシュ関数を用意する• きちんと作れば頑健で高信頼
– 詳しくはクヌース先生の TAoCP本を。
Cuckoo Hashing
Hash1(x) Hash1(y)
Hash2(x) Hash2(y)
• 2つの配列に 2つのハッシュ関数。 1つのアイテムは 2つの配列のうちどちらかに入っていれば良い。
• 衝突時には もう一つの配列を使う• 両方埋まってたら既存のをもう一つの配列へ• それも埋まってたらその片方をもう一つの配列へ (以後再帰的に全部• 検索はいつも 2つの配列を 2つのハッシュ関数で探すだけ
Cuckoo Hashing• 利点
– 検索は 2つの配列の一箇所ずつを探すだけなので高速– 多少ハッシュ値が偏ってももう一つのハッシュ値が散れば問題ない
• 欠点– 密度に応じて挿入のコストが上がる– 2つの配列を使う分メモリを食う
• チェインのポインタを持たなくてよいのでそんなに問題でないかも
– 特性上、全体の半分が埋まったところで性能がガタ落ちする• タチが悪いと円環して終わらない事もあり得るのかな
– つまり半分埋まる前に拡大する必要がある• スッカスカのテーブルを維持しなきゃいけないからありがたみ少ない
そこでHopscotch!
OpenAddressingの LinearProbingのキャッシュヒット力と OpenAddressingの検索力を両立する
新しい OpenAddressingなハッシュテーブル!
What is Hopscotch?
いわゆる「けんけんぱ」飛び方を決めてその通りに飛ぶ遊び名前かっこいい!
データ構造• 配列上の全バケットがデータの他に Hop情報を持つ。
– Hop情報は物理的には 1ワード幅のビット列• 図では大きさの都合上 8bitということに
– ここから隣のどのバケットに、本来ここに入るべきだったアイテムが置かれているかを示す。
1 1 0 0 1 0 1 0アイテム
1word
1なら対応データ有り0なら
検索• 普通の Hashmapと同様にバケットを検索する• そのバケットの Hop情報を見て、このバケット位置に対応するデータを順番に調べていき目的のものを見つける
• 1wordの bit数分の比較を行えば検索の成功 or失敗を決定できる– 比較回数は O( 1)で済む
1 2 3 4
1 1 0 0 1 0 1 0アイテム
4つ bitが立っていたので比較4回
検索• チェインハッシュで言うところのこれと状態は同じ
– 一つのバケット位置の下に複数のアイテムがぶら下がる– 配列に連続している分、左の Hopscotchのほうがキャッシュヒット率が高い
1
2
3
4
1 2 3 4
1 1 0 0 1 0 1 0アイテムアイテム
アイテム
アイテム
アイテム
挿入• およそ 8ワード分のビット数先のバケットまで配列を舐めながら、アイテムが入っていない空バケットが無いかを探す– 無いならテーブルを拡張して全部再配置してやり直し
1 1 0 0 1 0 1 0アイテム
ここに入れたいけど空きが無い
1 1 0 1 0 1 0 0アイテム
ビッシリ
挿入• およそ 8ワード分のビット数先のバケットまで配列を舐めながら、アイテムが入っていない空バケットが無いかを探す– 空バケットとは Hop情報部分はともかくアイテム部分が空の物
ここに入れたいけど空きが無い ビッシ
リ
8word bit先まで空きを探す
挿入• およそ 8ワード分のビット数先のバケットまで配列を舐めながら、アイテムが入っていない空バケットが無いかを探す– 空バケットとは Hop情報部分はともかくアイテム部分が空の物
ここに入れたいけど空きが無い ビッシ
リ
8word bit先まで空きを探す
空バケットあった!
挿入• およそ 8ワード分のビット数先のバケットまで配列を舐めながら、アイテムが入っていない空バケットが無いかを探す– 空バケットとは Hop情報部分はともかくアイテム部分が空の物
• 空バケットを swapしながら左に移動させていく– バケットから Hop情報で飛べる 1wordビット範囲でしか swapはしない
ここに入れたいけど空きが無い ビッシ
リ
0 1 0 1 0 1 0 0アイテム
swap
挿入• およそ 8ワード分のビット数先のバケットまで配列を舐めながら、アイテムが入っていない空バケットが無いかを探す– 空バケットとは Hop情報部分はともかくアイテム部分が空の物
• 空バケットを swapしながら左に移動させていく– バケットから Hop情報で飛べる 1wordビット範囲でしか swapはしない
ここに入れたいけど空きが無い ビッシ
リ
0 0 0 1 0 1 1 0アイテム
swap完了
挿入• およそ 8ワード分のビット数先のバケットまで配列を舐めながら、アイテムが入っていない空バケットが無いかを探す– 空バケットとは Hop情報部分はともかくアイテム部分が空の物
• 空バケットを swapしながら左に移動させていく– バケットから Hop情報で飛べる 1wordビット範囲でしか swapはしない
• 挿入したい位置から 1word幅になるまで続けるここに入れたいけど空きが無い ビッシ
リ swap
挿入• およそ 8ワード分のビット数先のバケットまで配列を舐めながら、アイテムが入っていない空バケットが無いかを探す– 空バケットとは Hop情報部分はともかくアイテム部分が空の物
• 空バケットを swapしながら左に移動させていく– バケットから Hop情報で飛べる 1wordビット範囲でしか swapはしない
• 挿入したい位置から 1word幅になるまで続けるここに入れたいけど空きが無い ビッシ
リ swap完了
挿入• これで空き部分に挿入できる
挿入• これで空き部分に挿入できる• アイテムを書き込んで Hop情報を更新して完了
1 1 0 0 1 0 1 1アイテム
削除• 普通に検索して該当するアイテムを消去して Hop情報の bitを落とすだけ
1 1 0 0 1 0 1 0アイテム
削除• 普通に検索して該当するアイテムを消去して Hop情報の bitを落とすだけ– 超簡単
1 1 0 0 0 0 1 0アイテム
ポイント• バケットそれぞれにアイテムと Hop情報が入っている。
– Hash値を算出して配列の該当部分にアクセスした瞬間に、その周辺もキャッシュラインに同時に載るためキャッシュミスが減る
• 検索時は必ずそのバケットから 1wordの bit数以内の距離にある– カッコウハッシュはバケット 2つしか 1つのアイテムが入りうる候補が無かったのに対して、 1word幅 bit個まで候補が増えている• 配列を拡張しなくても入る量が多い
• そんなわけで実装してみた。 Boost::unordered_mapより速い。– https://gist.github.com/2943289
Top Related