35
よよよよよ Hopscotch Hashing @kumagi

よくわかるHopscotch hashing

Embed Size (px)

Citation preview

Page 1: よくわかるHopscotch hashing

よくわかるHopscotch Hashing

@kumagi

Page 2: よくわかるHopscotch hashing

Locking!!!!

このアルゴリズムはLock-freeではありません!

Lock-freeを期待して見にきた方は回れ右!

Page 3: よくわかるHopscotch hashing

Hashmapとは?• みんな大好き連想配列!

– Rubyでも Perlでも Pythonでも人気者!– C++でなら unordered_map、 Javaなら HashMap

• 何かの値をキーとして別の値を保存・検索できるデータ構造– 「山!」→「川!」

• とにかく高速!– O(1)で動作するデータ構造

• 大量のデータを入れても検索速度が変わらないただ一つのデータ構造

Page 4: よくわかるHopscotch hashing

その仕組み

Hash関数

保存

山!

川!

8番目!

Hash関数に通すだけで保存 or検索すべき場所が判明する!

Page 5: よくわかるHopscotch hashing

欠点

混んでくるとぶつかる

Page 6: よくわかるHopscotch hashing

ぶつかったら?

大きなところへ引っ越せばいい

広々!

Page 7: よくわかるHopscotch hashing

でもそれは最後の手段• Hash値が衝突するのはそんなに珍しくない

– そのために配列確保しなおしてたら遅すぎる– メモリ効率悪すぎる

• メモリにやさしい解決方法が望まれている

Page 8: よくわかるHopscotch hashing

その戦略• 大きく分けて二つ

Open Addressing

Closed Addressing

or

Page 9: よくわかるHopscotch hashing

Closed Addressing

新しく作ってポインタで繋ぐ!

ぶつかっても

Page 10: よくわかるHopscotch hashing

Closed Addressing• 1つのアドレスにつき 1つの場所 (とそこからポインタで繋がった場所 )にしか対象の物が置かれないから Closedと呼ばれる

• ポインタで繋がるリスト構造から「チェインハッシュ」とも呼ばれる

• 利点– チェインの長さにだけ気をつければ O(1)の速度を保てる– ポインタを繋ぎ換えれば LinearHashなどが作れるし簡単

• 欠点– ポインタを辿るのが遅い

Page 11: よくわかるHopscotch hashing

対するOpen Addressingは…

Page 12: よくわかるHopscotch hashing

Open Addressing

ぶつかっても

隣を使う!

やったね!

Page 13: よくわかるHopscotch hashing

選べる探索バリエーション (併用不可 )

+1 +1

±1, 2, 4, 8, 16…

±Hash(x)

Liner Probing

Quadratic Probing

Double Hashing

Page 14: よくわかるHopscotch hashing

Open Addressing• 一つの Hash値に対して配列上の複数の場所が該当しうるので Openと名がつくんだと思う

• 利点– キャッシュの局所性を生かせるので速い(特に Linear Probing)– ポインタの分だけ省メモリ

• 欠点– 削除時にはデータを消さずに削除フラグを立てて再利用させるだけ• データが存在している事そのものが「まだ配列の続きにあるかもよ」という状態を表しているので完全に消しちゃうとマズい

• よって挿入・削除で密度が上がってくると入ってるデータが少ない時でも検索・挿入・削除の性能がガタ落ちする

Page 15: よくわかるHopscotch hashing

ベンチマーク比較

使用済み要素の割合

キャッシュミス頻度

Page 16: よくわかるHopscotch hashing

Cuckoo Hashing

• 鳥のカッコウの習性のように、托卵する際に他の鳥の卵があった場合に退けるハッシュマップ– OpenAddressingにも ClosedAddressingにも分類しがたい

• 2つの配列と 2つのハッシュ関数を用意する• きちんと作れば頑健で高信頼

– 詳しくはクヌース先生の TAoCP本を。

Page 17: よくわかるHopscotch hashing

Cuckoo Hashing

Hash1(x) Hash1(y)

Hash2(x) Hash2(y)

• 2つの配列に 2つのハッシュ関数。 1つのアイテムは 2つの配列のうちどちらかに入っていれば良い。

• 衝突時には もう一つの配列を使う• 両方埋まってたら既存のをもう一つの配列へ• それも埋まってたらその片方をもう一つの配列へ (以後再帰的に全部• 検索はいつも 2つの配列を 2つのハッシュ関数で探すだけ

Page 18: よくわかるHopscotch hashing

Cuckoo Hashing• 利点

– 検索は 2つの配列の一箇所ずつを探すだけなので高速– 多少ハッシュ値が偏ってももう一つのハッシュ値が散れば問題ない

• 欠点– 密度に応じて挿入のコストが上がる– 2つの配列を使う分メモリを食う

• チェインのポインタを持たなくてよいのでそんなに問題でないかも

– 特性上、全体の半分が埋まったところで性能がガタ落ちする• タチが悪いと円環して終わらない事もあり得るのかな

– つまり半分埋まる前に拡大する必要がある• スッカスカのテーブルを維持しなきゃいけないからありがたみ少ない

Page 19: よくわかるHopscotch hashing

そこでHopscotch!

OpenAddressingの LinearProbingのキャッシュヒット力と OpenAddressingの検索力を両立する

新しい OpenAddressingなハッシュテーブル!

Page 20: よくわかるHopscotch hashing

What is Hopscotch?

いわゆる「けんけんぱ」飛び方を決めてその通りに飛ぶ遊び名前かっこいい!

Page 21: よくわかるHopscotch hashing

データ構造• 配列上の全バケットがデータの他に Hop情報を持つ。

– Hop情報は物理的には 1ワード幅のビット列• 図では大きさの都合上 8bitということに

– ここから隣のどのバケットに、本来ここに入るべきだったアイテムが置かれているかを示す。

1 1 0 0 1 0 1 0アイテム

1word

1なら対応データ有り0なら

Page 22: よくわかるHopscotch hashing

検索• 普通の Hashmapと同様にバケットを検索する• そのバケットの Hop情報を見て、このバケット位置に対応するデータを順番に調べていき目的のものを見つける

• 1wordの bit数分の比較を行えば検索の成功 or失敗を決定できる– 比較回数は O( 1)で済む

1 2 3 4

1 1 0 0 1 0 1 0アイテム

4つ bitが立っていたので比較4回

Page 23: よくわかるHopscotch hashing

検索• チェインハッシュで言うところのこれと状態は同じ

– 一つのバケット位置の下に複数のアイテムがぶら下がる– 配列に連続している分、左の Hopscotchのほうがキャッシュヒット率が高い

1

2

3

4

1 2 3 4

1 1 0 0 1 0 1 0アイテムアイテム

アイテム

アイテム

アイテム

Page 24: よくわかるHopscotch hashing

挿入• およそ 8ワード分のビット数先のバケットまで配列を舐めながら、アイテムが入っていない空バケットが無いかを探す– 無いならテーブルを拡張して全部再配置してやり直し

1 1 0 0 1 0 1 0アイテム

ここに入れたいけど空きが無い

1 1 0 1 0 1 0 0アイテム

ビッシリ

Page 25: よくわかるHopscotch hashing

挿入• およそ 8ワード分のビット数先のバケットまで配列を舐めながら、アイテムが入っていない空バケットが無いかを探す– 空バケットとは Hop情報部分はともかくアイテム部分が空の物

ここに入れたいけど空きが無い ビッシ

8word bit先まで空きを探す

Page 26: よくわかるHopscotch hashing

挿入• およそ 8ワード分のビット数先のバケットまで配列を舐めながら、アイテムが入っていない空バケットが無いかを探す– 空バケットとは Hop情報部分はともかくアイテム部分が空の物

ここに入れたいけど空きが無い ビッシ

8word bit先まで空きを探す

空バケットあった!

Page 27: よくわかるHopscotch hashing

挿入• およそ 8ワード分のビット数先のバケットまで配列を舐めながら、アイテムが入っていない空バケットが無いかを探す– 空バケットとは Hop情報部分はともかくアイテム部分が空の物

• 空バケットを swapしながら左に移動させていく– バケットから Hop情報で飛べる 1wordビット範囲でしか swapはしない

ここに入れたいけど空きが無い ビッシ

0 1 0 1 0 1 0 0アイテム

swap

Page 28: よくわかるHopscotch hashing

挿入• およそ 8ワード分のビット数先のバケットまで配列を舐めながら、アイテムが入っていない空バケットが無いかを探す– 空バケットとは Hop情報部分はともかくアイテム部分が空の物

• 空バケットを swapしながら左に移動させていく– バケットから Hop情報で飛べる 1wordビット範囲でしか swapはしない

ここに入れたいけど空きが無い ビッシ

0 0 0 1 0 1 1 0アイテム

swap完了

Page 29: よくわかるHopscotch hashing

挿入• およそ 8ワード分のビット数先のバケットまで配列を舐めながら、アイテムが入っていない空バケットが無いかを探す– 空バケットとは Hop情報部分はともかくアイテム部分が空の物

• 空バケットを swapしながら左に移動させていく– バケットから Hop情報で飛べる 1wordビット範囲でしか swapはしない

• 挿入したい位置から 1word幅になるまで続けるここに入れたいけど空きが無い ビッシ

リ swap

Page 30: よくわかるHopscotch hashing

挿入• およそ 8ワード分のビット数先のバケットまで配列を舐めながら、アイテムが入っていない空バケットが無いかを探す– 空バケットとは Hop情報部分はともかくアイテム部分が空の物

• 空バケットを swapしながら左に移動させていく– バケットから Hop情報で飛べる 1wordビット範囲でしか swapはしない

• 挿入したい位置から 1word幅になるまで続けるここに入れたいけど空きが無い ビッシ

リ swap完了

Page 31: よくわかるHopscotch hashing

挿入• これで空き部分に挿入できる

Page 32: よくわかるHopscotch hashing

挿入• これで空き部分に挿入できる• アイテムを書き込んで Hop情報を更新して完了

1 1 0 0 1 0 1 1アイテム

Page 33: よくわかるHopscotch hashing

削除• 普通に検索して該当するアイテムを消去して Hop情報の bitを落とすだけ

1 1 0 0 1 0 1 0アイテム

Page 34: よくわかるHopscotch hashing

削除• 普通に検索して該当するアイテムを消去して Hop情報の bitを落とすだけ– 超簡単

1 1 0 0 0 0 1 0アイテム

Page 35: よくわかるHopscotch hashing

ポイント• バケットそれぞれにアイテムと Hop情報が入っている。

– Hash値を算出して配列の該当部分にアクセスした瞬間に、その周辺もキャッシュラインに同時に載るためキャッシュミスが減る

• 検索時は必ずそのバケットから 1wordの bit数以内の距離にある– カッコウハッシュはバケット 2つしか 1つのアイテムが入りうる候補が無かったのに対して、 1word幅 bit個まで候補が増えている• 配列を拡張しなくても入る量が多い

• そんなわけで実装してみた。 Boost::unordered_mapより速い。– https://gist.github.com/2943289