C#で高速かつ十分に分散されたハッシュテーブルを実装したいと考えています。任意のハッシュコードを取得してバケットにインデックスを付けるために使用できるように「制約」するハッシュ制約関数を選択するのに問題があります。これまでに確認したオプションは2つあります。
一方では、バケットに常に素数の要素が含まれていることを確認し、ハッシュを制約するために、バケット数でハッシュします。これは実際、 。NETの辞書が行うこと です。 このアプローチの問題は、%の使用が他の操作と比較して非常に遅いことです(Agner Fog命令を見るとtables 、idiv
(%に対して生成されるアセンブリコード)の命令レイテンシは、新しいIntelプロセッサでは約25サイクルです。 mul
の場合は約3、and
、or
、xor
などのビット単位の演算の場合は1と比較してください。
一方、バケットの数は常に2の累乗にすることができます。配列の外部にインデックスを付けないようにするには、ハッシュの係数を計算する必要がありますが、今回は安価です。 。 2の累乗の場合、_% N
_は& (N - 1)
なので、制約は1〜2サイクルしかかからないマスキング操作に削減されます。これは Googleのsparsehash によって行われます。 これの欠点は、優れたハッシュを提供するためにユーザーに頼っているということです;ハッシュをマスクすると、本質的にハッシュの一部がカットされるため、もはやハッシュのすべてのビットを考慮に入れます。ユーザーのハッシュが不均一に分布している場合、たとえば上位ビットのみが入力されている、または下位ビットが一貫して同じである場合、このアプローチの衝突率ははるかに高くなります。
私は両方の世界で最高のものを使用できるアルゴリズムを探しています:ハッシュのすべてのビットを考慮に入れ、%を使用するよりも高速です必ずしもモジュラスである必要はなく、_0..N-1
_(Nはバケットの長さ)の範囲にあることが保証され、すべてのスロットに均等に分散されるものだけです。そのようなアルゴリズムは存在しますか?
助けてくれてありがとう。
最新のハッシュテーブルの実装では、modulo関数を使用しません。彼らはしばしば2つのサイズのテーブルの能力を使用し、不要なビットを切り落とします。理想的なハッシュ関数はこれを可能にします。素数テーブルサイズと組み合わせたmoduloの使用は、ハッシュ関数が一般に貧弱であった時代に発生しました。これは、それらが.net開発に含まれることが多いためです。最新のハッシュ関数である SipHash について読んだ後、 xxHash などの他の最新の関数について読むことをお勧めします。
.netハッシュ関数がしばしば貧弱である理由を説明する必要があります。 .netでは、プログラマはGetHashcodeをオーバーライドしてハッシュ関数を実装することを強いられることがよくあります。しかし、.netは、プログラマーが作成した関数が高品質であることを保証するために必要なツールを提供していません。
ハッシュテーブルのインデックスとしてハッシュ関数の結果を使用する方法の詳細については、このペーパーのハッシュのユニバーサル形式の定義を参照してください: キャリーレス乗算を使用したより高速な64ビットユニバーサルハッシュ
すべてのビットを保持しながらANDを使用するには、XORも使用します。
たとえば、temp = (hash & 0xFFFF) ^ ( hash >> 16); index = (temp & 0xFF) ^ (temp >> 8);
です。
この例では、モジュロはなく、hash
の32ビットすべてが8ビットのindex
に影響します。ただし、DIVよりも高速であるかどうかは、あまりにも多くの要因に依存するものであり、場合によっては簡単にDIVよりも遅くなる可能性があります(たとえば、大きなハッシュや小さなインデックス)。
多くの素整数がモジュラー乗法逆数を持っているという事実を利用できます。 この記事 を参照してください。本質的に比較的素数であるバケットインデックスと係数2 ^ nを素数にすることにより、制約の1つを満たしています。
この記事では、その数を掛けてオーバーフローを無視すると、バケットのインデックスサイズで割った場合と同じ結果が得られるような数を見つけるアルゴリズムについて説明します。