FxHashとは
FxHash
は暗号的に安全でない高速なハッシュ関数です。
64bit環境では64bitいっぺんに処理できることが強みでFirefoxやrustcの内部で使うハッシュマップなどに使われています。
rustcではFNV(これはバイト単位で処理する)からFxHash
に変更することで衝突が少し増えたもののスピードが速いので全体としては性能の改善に繋がりました。 PR
参考:
https://searchfox.org/mozilla-central/source/mfbt/HashFunctions.h
https://github.com/cbreeden/fxhash
https://github.com/rust-lang/rustc-hash
アルゴリズム
FxHash
のアルゴリズムは超簡単です。
わかりやすさのためにrustのコードをrustc_hashより抜粋
pub struct FxHasher {
hash: usize,
}
#[cfg(target_pointer_width = "32")]
const K: usize = 0x9e3779b9;
#[cfg(target_pointer_width = "64")]
const K: usize = 0x517cc1b727220a95;
impl Default for FxHasher {
#[inline]
fn default() -> FxHasher {
FxHasher { hash: 0 }
}
}
impl FxHasher {
#[inline]
fn add_to_hash(&mut self, i: usize) {
self.hash = self.hash.rotate_left(5).bitxor(i).wrapping_mul(K);
}
}
メインの処理は、
self.hash = self.hash.rotate_left(5).bitxor(i).wrapping_mul(K);
だけです。
ここでK
の値は、
32bitの場合は黄金比、
# python
hex(int((math.sqrt(5) - 1) / 2 * (2 ** 32)))
64bitの場合は円周率
# python
hex(int(1 / math.pi * 2**64))
から導出されています。
解説
入力の最初の32/64ビット
まず、self.hash
が0
のデフォルトの状態、つまりハッシュ対象が32/64bit以内の場合を考えると、
self.hash = i.wrapping_mul(K);
とただ掛け算しているだけになり、これはK
が黄金比の場合、Fibonacci Hashing
と呼ばれ出力がよく分散するらしいです。(よくわかってない)
ただ、64bitの場合はK
を黄金比にすると偶数となってしまい、そうすると出力が必ず偶数になってしまい良くないので円周率(これは奇数になる)を使っているらしいです。参考 (結局K
が無理数(に近い整数)なら割と何でも良さそう?)
また、オリジナルのソースコードではK
は
- 奇数
- 各ビットの1/0の割合が大体半々
なら何でも良いと書いてあります。
入力の32/64ビット以降
ハッシュ対象が32/64bitより大きい場合は32/64bitづつに区切ってハッシュ値を蓄積していくことになります
再掲
self.hash = self.hash.rotate_left(5).bitxor(i).wrapping_mul(K);
ここではまずself.hash
を左に5
回転させています。特別5
でなくても構いませんが偶数だと回転し続けたときのパターン数が減ってしまうので奇数のほうが良いです(もっと言えば素数だと良い?)。
ちなみにこの左回転はThe Art of Computer Programmingの6.4に載っているG. D. Knottが提唱した、複数語のハッシュ値を単純に加算やxor
で合成すると(X, Y)と(Y, X)のハッシュ値が衝突してしまうので事前に回転させるという手法をもってきたものだと思います。たしかに、ここで回転しないと(X, Y)と(Y, X)のハッシュ値が同じになってしまいます。
それから入力とxor
を取ったあとで掛け算をします。xor
が掛け算の後だとself.hash
が0
のときに入力をそのまま出力してしまうので良くありません。
これで終わりです。
歴史
- 2012年 Firefoxの内部で実装される。このときは他に
spookyhash
、murmurhash3
、cityhash
などが検討されていた - 2016年 rustc内で使われるようになる。このときに64bitへの拡張が行われる。
FxHash
という名前もここで初めて付いた?