Redis: HyperLogLog の実装について その3
前回は sparse 表現のバイナリについて中身をざっと見た。
今回は HyperLogLog の目的である、カーディナリティを求める処理 pfcount についてその実装を確かめてみる。
カウントのコア部分の処理は hllCount 関数に記述されている。
カウントの求め方
論文によれば、カーディナリティの期待値 E は以下のように求められる。
- レジスタの個数を m とする。Redis の場合 m=214=16384
- このとき E=αmm2∑mj=12−M(j)
- ここで αm=(m∫∞0(log2(2+u1+u))mdu)−1
Redis の場合、これは αm≈0.7213⋅(1+1.079m)−1≈0.7213 で近似される。 - また、M(j) は j番目のレジスタの値である。
具体的な例
これまで見てきたように、Redis の HyperLogLog 型に "A", "B", "C" という 3つの値を追加すると、レジスタの状態は以下のようになる。
- index:4477 -> 3 (C)
- index:12352 -> 1 (A)
- index:12964 -> 3 (B)
- 上記以外の index -> 0
この例で実際に E を求めてみる。
- 20=1,2−1=0.5,2−3=0.125 であるから m∑j=12−M(j)=1⋅(m−3)+0.5⋅1+0.125⋅2=16381.75
- したがって E=αmm2∑mj=12−M(j)≈0.7213⋅16384216381.75≈11819.40
実際のカーディナリティは 3 なので、大きなズレがあるように見える。
期待値の補正
このように、HyperLogLog は小さいカーディナリティに対して大きな誤差が出るという性質がある。
この問題を解決するため、Redis では一旦期待値を算出した後で、以下のルールに従ってその値を補正している。
- i) E<2.5⋅m=40960 かつ 値が 0 であるレジスタが存在する場合
以下の式 (Redis のコメントには「LINEARCOUNTING アルゴリズム」と書かれている) によって期待値を計算し直す。値が 0 であるレジスタの個数を z として E:=m⋅log(mz)
上記の具体例では、z=16384−3=16381 なので E:=m⋅log(mz)=16384⋅log(1638416381)≈3.0003
正しいカーディナリティ 3 を得ることができた。 - ii) i)に該当せず E<72000 の場合
以下の多項式バイアスを適用して期待値を調整している。これは m=214 の場合に限り適用可能。 bias=5.9119⋅10−18E4−1.4253⋅10−12E3+1.2940⋅10−7E2−5.2921⋅10−3E+83.3216E:=E−E⋅bias100 - iii) その他の場合は補正なし
こうして得られた値 E を 64ビット非負整数に変換したものが、HyperLogLog型のカウントとして得られる値である。
0 件のコメント:
コメントを投稿