%演算子を使用せずに、よく分散されたハッシュテーブルを実装することは可能ですか?


11

私は、C#で高速で十分に分散されたハッシュテーブルを実装したいと考えています。任意のハッシュコードを受け取り、それを「制約」して、バケットのインデックス作成に使用できるハッシュ制約関数の選択に問題があります。私がこれまでに見た2つのオプションがあります:

  • 一方では、バケットに常に素数の要素があることを確認し、ハッシュを制約するには、単にバケットの数でモジュロします。実際、これは.NETの辞書が行うことです。このアプローチの問題は、%の使用が他の操作と比較して非常に遅いことです。あなたが見ればAgner霧命令テーブルidiv(%のために生成されますアセンブリコードで)新しいIntelプロセッサのための〜25サイクルの命令のレイテンシを持っています。3の周りにこれを比較しmul、等をビット単位のオペレーションのための1 andorまたはxor

  • 一方、バケットの数は常に2の累乗にすることができます。配列の外部でインデックスを作成しないようにハッシュのモジュラスを計算する必要がありますが、今回はより安価になります。2のべき乗のためのため% Nだけされ& (N - 1)、拘束のみ1~2サイクルかかるマスキング演算に低減されます。これはGoogleのsparsehashによって行われますこの欠点は、ユーザーに適切なハッシュを提供することを期待していることです。ハッシュをマスクすると、基本的にハッシュの一部が切り捨てられるため、ハッシュのすべてのビットを考慮しなくなります。ユーザーのハッシュが不均等に分散している場合、たとえば上位ビットのみが埋められるか、下位ビットが常に同じである場合、このアプローチの衝突率ははるかに高くなります。

私は、両方の長所を備えた使用可能なアルゴリズムを探しています。ハッシュのすべてのビットを考慮し、%を使用するよりも高速です。必ずしもモジュラスである必要はなく、範囲内0..N-1(Nはバケットの長さ)にあることが保証されているだけで、すべてのスロットに均等に分布しています。そのようなアルゴリズムは存在しますか?

助けてくれてありがとう。


1
murmurhash3(smhasher)の説明だけでなく、雪崩効果調べてください。ただし、より良いハッシュ関数を採用することで、質問の基本的なポイントに対処することはできません。代わりに、そもそもなぜユーザーが同じ優れたハッシュ関数を採用しないのかという問題と、対策の要請(ユーザーが悪意を持って怠け者であるかのように)です。
rwong


高速モジュロ(2^N +/- 1)については、stackoverflow.com
questions / 763137 /…を

@rwong申し訳ありませんが、あなたのコメントが私の投稿とどう関係するのかよくわかりません。私はユーザーが提供するハッシュを制御しないので、より良いハッシュ関数を探していません。また、「悪意のある怠zyなユーザー」とはどういう意味かわかりません。
ジェームズKo

4
ハッシュ関数が貧弱な場合、ハッシュテーブルの実装者が貧弱な分布を「修正」するためにできることは何もありません。素数のモジュロは、不十分なハッシュを修復しません。出力として素数の倍数を生成するハッシュ関数を考えます。実際の製品コードでこのような問題を見てきました。
フランクヒルマン

回答:


9

最新のハッシュテーブルの実装では、モジュロ関数は使用されません。多くの場合、2つのサイズのテーブルのパワーを使用し、不要なビットを切り落とします。理想的なハッシュ関数はこれを可能にします。モジュロと素数テーブルサイズの組み合わせは、ハッシュ関数が.net開発でよくあるように、一般的に貧弱だった時代に生じました。最新のハッシュ関数であるSipHashについて読んでから、xxHashなどの他の最新の関数について読むことをお勧めします

.netハッシュ関数がしばしば貧弱な理由を説明する必要があります。.netでは、プログラマーはしばしばGetHashcodeをオーバーライドすることにより、ハッシュ関数の実装を強制されます。しかし、.netは、プログラマーが作成した関数が高品質であることを保証するために必要なツールを提供していません。

  • 構造またはクラスでのハッシュ状態のカプセル化
  • ハッシュ「追加」関数。ハッシュ状態に新しいデータを追加します(たとえば、バイト配列やダブルを追加します)
  • 雪崩を生成するハッシュ「ファイナライズ」関数
  • ハッシュ結果のカプセル化-.netでは、1つの選択肢、32ビット符号付き整数が得られます。

ハッシュテーブルのインデックスとしてハッシュ関数の結果を使用する方法の詳細については、本論文ではハッシングの普遍的な形式の定義を参照してください:より高速な64ビットの汎用ハッシュキャリーレス乗算を使用して


3

すべてのビットを保持したままANDを使用するには、XORも使用します。

たとえば、temp = (hash & 0xFFFF) ^ ( hash >> 16); index = (temp & 0xFF) ^ (temp >> 8);

この例では、モジュロはなく、32ビットすべてhashが8 ビットに影響しますindex。ただし、DIVよりも高速であるかどうかは、非常に多くの要因に依存するものであり、場合によってはDIVよりも簡単に遅くなる可能性があります(たとえば、大きなハッシュと小さなインデックス)。


これは常にDIV / IDIVよりも高速になりますが、私の質問に答えるとは思いません。index範囲内になります[0..255]。範囲内の何かが必要です。[0..n-1]ここnで、バケットの数です。
ジェームズKo

@JamesKoただし、辞書を実装する場合は、バケットの数も(ある程度)制御します。したがって、素数の代わりに、2のべき乗を選択できます。(そうすることは実際には良い考えであるかどうか、私はあなたに言うことはできません。)
svick

@svick 2の累乗の場合、単純なマスク操作を実行できます。質問で述べたように、私は素数でこれを行うための安価な方法を探しているので、不十分に分散されたハッシュでも収容されます。
ジェームズKo

1

多くの素数整数にはモジュラー乗法逆数があるという事実を利用できます。こちらの記事をご覧ください。バケットインデックスをプライムにし、モジュラスを2 ^ nにすることで、制約の1つを満たしました。これらは本質的に比較的プライムです。

この記事では、その数で乗算し、オーバーフローを無視すると、バケットインデックスサイズで除算した場合と同じ結果になるような数を見つけるアルゴリズムについて説明します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.