よくわかる
Hopscotch Hashing
          @kumagi
Locking!!!!
   このアルゴリズムは
Lock-freeではありません!
 Lock-freeを期待して見にきた方は回れ右!
Hashmapとは?
• みんな大好き連想配列!
 – RubyでもPerlでもPythonでも人気者!
 – C++でならunordered_map、JavaならHashMap
• 何かの値をキーとして別の値を保存・検索できるデータ構造
 – 「山!」→「川!」
• とにかく高速!
 – O(1)で動作するデータ構造
   • 大量のデータを入れても検索速度が変わらないただ一つのデータ
     構造
その仕組み
山!      8番目!



          川!




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




混んでくるとぶつかる
ぶつかったら?


 広々!




大きなところへ
引っ越せばいい
でもそれは最後の手段
• Hash値が衝突するのはそんなに珍しくない
 – そのために配列確保しなおしてたら遅すぎる
 – メモリ効率悪すぎる


• メモリにやさしい解決方法が望まれている
その戦略
• 大きく分けて二つ




     Open              Closed
   Addressing        Addressing
                or
Closed Addressing




新しく作ってポインタで繋ぐ!
Closed Addressing
• 1つのアドレスにつき1つの場所(とそこからポインタで繋
  がった場所)にしか対象の物が置かれないからClosedと呼ば
  れる
• ポインタで繋がるリスト構造から「チェインハッシュ」とも
  呼ばれる
• 利点
 – チェインの長さにだけ気をつければO(1)の速度を保てる
 – ポインタを繋ぎ換えればLinearHashなどが作れるし簡単
• 欠点
 – ポインタを辿るのが遅い
対するOpen Addressingは…
Open Addressing

          やったね!




    隣を使う!
選べる探索バリエーション(併用不
       可)
                        Liner Probing




                        Quadratic Probing
     ±1, 2, 4, 8, 16…



                        Double Hashing
             ±Hash(x)
Open Addressing
• 一つのHash値に対して配列上の複数の場所が該当しうるの
  でOpenと名がつくんだと思う
• 利点
 – キャッシュの局所性を生かせるので速い(特にLinear Probing)
 – ポインタの分だけ省メモリ
• 欠点
 – 削除時にはデータを消さずに削除フラグを立てて再利用させる
   だけ
   • データが存在している事そのものが「まだ配列の続きにあるかも
     よ」という状態を表しているので完全に消しちゃうとマズい
   • よって挿入・削除で密度が上がってくると入ってるデータが少な
     い時でも検索・挿入・削除の性能がガタ落ちする
ベンチマーク比較
             キ
             ャ
             ッ
             シ
             ュ
             ミ
             ス
             頻
             度




 使用済み要素の割合
Cuckoo Hashing
• 鳥のカッコウの習性のように、托卵する際に他の鳥の卵が
  あった場合に退けるハッシュマップ
 – OpenAddressingにもClosedAddressingにも分類しがたい
• 2つの配列と2つのハッシュ関数を用意する
• きちんと作れば頑健で高信頼
 – 詳しくはクヌース先生のTAoCP本を。
Cuckoo Hashing
• 2つの配列に2つのハッシュ関数。1つのアイテムは2つの配列
  のうちどちらかに入っていれば良い。
• 衝突時には
  もう一つの配列を使う               Hash1(y)
                  Hash1(x)
• 両方埋まってたら
既存のをもう一つの配列へ    Hash2(x)     Hash2(y)
• それも埋まって
たらその片方をもう一つの配列へ(以後再帰的に全部
• 検索はいつも2つの配列を2つのハッシュ関数で探すだけ
• 利点
       Cuckoo Hashing
 – 検索は2つの配列の一箇所ずつを探すだけなので高速
 – 多少ハッシュ値が偏ってももう一つのハッシュ値が散れば問題
   ない
• 欠点
 – 密度に応じて挿入のコストが上がる
 – 2つの配列を使う分メモリを食う
   • チェインのポインタを持たなくてよいのでそんなに問題でないか
     も
 – 特性上、全体の半分が埋まったところで性能がガタ落ちする
   • タチが悪いと円環して終わらない事もあり得るのかな
 – つまり半分埋まる前に拡大する必要がある
   • スッカスカのテーブルを維持しなきゃいけないからありがたみ少
     ない
そこでHopscotch!



OpenAddressingのLinearProbingのキャッシュヒット
     力とOpenAddressingの検索力を両立する
   新しいOpenAddressingなハッシュテーブル!
What is Hopscotch?
         いわゆる「けんけんぱ」
         飛び方を決めてその通りに
         飛ぶ遊び
         名前かっこいい!
データ構造
• 配列上の全バケットがデータの他にHop情報を持つ。
 – Hop情報は物理的には1ワード幅のビット列
   • 図では大きさの都合上8bitということに
 – ここから隣のどのバケットに、本来ここに入るべきだったアイ
   テムが置かれているかを示す。
              1word
          アイテム   11001010   1なら対応データ有り
                            0なら
検索
• 普通のHashmapと同様にバケットを検索する
• そのバケットのHop情報を見て、このバケット位置に対応する
  データを順番に調べていき目的のものを見つける
• 1wordのbit数分の比較を行えば検索の成功or失敗を決定できる
   – 比較回数はO(1)で済む
          アイテム   11001010


                   12   3   4

            4つbitが立っていたので比較4
            回
検索
• チェインハッシュで言うところのこれと状態は同じ
 – 一つのバケット位置の下に複数のアイテムがぶら下がる
 – 配列に連続している分、左のHopscotchのほうがキャッシュ
   ヒット率が高い


      1   アイテム        アイテム   11001010
      2   アイテム

      3   アイテム                 12   3   4

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

                       アイテム   11010100
     アイテム   11001010


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




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

                        空バケットあっ
                          た!
              8word bit先まで空きを探
              す
ここに入れたいけ
 ど空きが無い
               ビッシ
               リ
挿入
• およそ8ワード分のビット数先のバケットまで配列を舐めな
  がら、アイテムが入っていない空バケットが無いかを探す
 – 空バケットとはHop情報部分はともかくアイテム部分が空の物
• 空バケットをswapしながら左に移動させていく
 – バケットからHop情報で飛べる1wordビット範囲でしかswapは
   しない                  アイテム
                           01010100


ここに入れたいけ
 ど空きが無い
                 ビッシ
                 リ         swap
挿入
• およそ8ワード分のビット数先のバケットまで配列を舐めな
  がら、アイテムが入っていない空バケットが無いかを探す
 – 空バケットとはHop情報部分はともかくアイテム部分が空の物
• 空バケットをswapしながら左に移動させていく
 – バケットからHop情報で飛べる1wordビット範囲でしかswapは
   しない                  アイテム
                           00010110


ここに入れたいけ
 ど空きが無い
                 ビッシ
                 リ         swap完了
挿入
• およそ8ワード分のビット数先のバケットまで配列を舐めな
  がら、アイテムが入っていない空バケットが無いかを探す
 – 空バケットとはHop情報部分はともかくアイテム部分が空の物
• 空バケットをswapしながら左に移動させていく
 – バケットからHop情報で飛べる1wordビット範囲でしかswapは
   しない
• 挿入したい位置から1word幅になるまで続ける
 ここに入れたいけ
  ど空きが無い
                 ビッシ
                 リ   swap
挿入
• およそ8ワード分のビット数先のバケットまで配列を舐めな
  がら、アイテムが入っていない空バケットが無いかを探す
 – 空バケットとはHop情報部分はともかくアイテム部分が空の物
• 空バケットをswapしながら左に移動させていく
 – バケットからHop情報で飛べる1wordビット範囲でしかswapは
   しない
• 挿入したい位置から1word幅になるまで続ける
 ここに入れたいけ
  ど空きが無い
                 ビッシ
                 リ   swap完了
挿入
• これで空き部分に挿入できる
挿入
• これで空き部分に挿入できる
• アイテムを書き込んでHop情報を更新して完了



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




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




          アイテム   11000010
ポイント
• バケットそれぞれにアイテムとHop情報が入っている。
  – Hash値を算出して配列の該当部分にアクセスした瞬間に、その
    周辺もキャッシュラインに同時に載るためキャッシュミスが減
    る
• 検索時は必ずそのバケットから1wordのbit数以内の距離にあ
  る
  – カッコウハッシュはバケット2つしか1つのアイテムが入りうる
    候補が無かったのに対して、1word幅bit個まで候補が増えてい
    る
     • 配列を拡張しなくても入る量が多い
• そんなわけで実装してみた。Boost::unordered_mapより速
  い。
  – https://gist.github.com/2943289

よくわかるHopscotch hashing