整数ハッシュキーを受け入れる適切な整数ハッシュ関数はどれですか。


回答:


47

クヌースの乗法:

hash(i)=i*2654435761 mod 2^32

一般に、ハッシュサイズのオーダ(2^32例)にあり、それに共通の要素がない乗数を選択する必要があります。このようにして、ハッシュ関数はすべてのハッシュスペースを均一にカバーします。

編集:このハッシュ関数の最大の欠点は、分割可能性が維持されることです。そのため、整数がすべて2または4で割り切れる場合(これは珍しいことではありません)、それらのハッシュも割り切れます。これはハッシュテーブルの問題です。使用されるバケットの1/2または1/4だけになる可能性があります。


36
有名な名前に付けられていますが、これは本当に悪いハッシュ関数です。
Seun Osewa 2010

5
プライムテーブルサイズで使用する場合、これは悪いハッシュ関数ではありません。また、これはクローズドハッシュ用です。ハッシュ値が均一に分散されていない場合、乗法ハッシュにより、ある値からの衝突が他のハッシュ値でアイテムを「妨害」する可能性が低くなります。
Paolo Bonzini

11
好奇心旺盛な方のために、この定数はハッシュサイズ(2 ^ 32)をPhiで割った値として選択されています
awdz9nld

7
パオロ:クヌースの方法は、上位ビットで雪崩にならないという意味で「悪い」
awdz9nld

9
よく調べてみると、実際には2654435761が素数であることがわかります。それは2654435769.のではなく、選ばれた理由は、おそらくですので
karadoc

149

次のアルゴリズムは非常に良い統計的分布を提供することがわかりました。各入力ビットは、約50%の確率で各出力ビットに影響します。衝突はありません(各入力は異なる出力になります)。このアルゴリズムは、CPUに整数乗算ユニットが組み込まれていない場合を除いて高速です。Cコード、int32ビットを想定(Javaの場合、で置き換え>>>>>削除unsigned):

unsigned int hash(unsigned int x) {
    x = ((x >> 16) ^ x) * 0x45d9f3b;
    x = ((x >> 16) ^ x) * 0x45d9f3b;
    x = (x >> 16) ^ x;
    return x;
}

マジックナンバーは、アバランシェ効果(単一の入力ビットが変更された場合に変化する出力ビットの数。平均でほぼ16である必要があります)を計算する、長時間実行される特別なマルチスレッドテストプログラムを使用して計算されました。出力ビットの変化(出力ビットは互いに依存してはならない)、および入力ビットが変更された場合の各出力ビットの変化の確率。計算された値は、MurmurHashで使用される32ビットのファイナライザよりも優れており、AESを使用する場合とほぼ同じです(まったく同じではありません)。わずかな利点は、同じ定数が2回使用されることです(前回のテストでは、それがわずかに速くなりましたが、まだ当てはまるかどうかはわかりません)。

0x45d9f3b0x119de1f3乗法逆数)に置き換えると、プロセスを逆にする(ハッシュから入力値を取得する)ことができます。

unsigned int unhash(unsigned int x) {
    x = ((x >> 16) ^ x) * 0x119de1f3;
    x = ((x >> 16) ^ x) * 0x119de1f3;
    x = (x >> 16) ^ x;
    return x;
}

64ビットの数値の場合、次の方法を使用することをお勧めします。これは最速ではないかもしれません。これはsplitmix64に基づいています。これはブログ記事Better Bit Mixing(mix 13)に基づいているようです。

uint64_t hash(uint64_t x) {
    x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
    x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
    x = x ^ (x >> 31);
    return x;
}

Javaの場合はlong、を使用Lして定数に追加し、で置き換え>>>>>削除しunsignedます。この場合、反転はより複雑です。

uint64_t unhash(uint64_t x) {
    x = (x ^ (x >> 31) ^ (x >> 62)) * UINT64_C(0x319642b2d24d8ec3);
    x = (x ^ (x >> 27) ^ (x >> 54)) * UINT64_C(0x96de1b173f119089);
    x = x ^ (x >> 30) ^ (x >> 60);
    return x;
}

更新:ハッシュ関数プロスペクタープロジェクトを確認することもできます。このプロジェクトには、他の(おそらくより適切な)定数がリストされています。


2
最初の2行はまったく同じです!ここにタイプミスはありますか?
Kshitij Banerjee

3
いいえ、これはタイプミスではありません。2行目はさらにビットを混合します。乗算を1つだけ使用するのはよくありません。
Thomas Mueller、

3
応じたので、私はマジックナンバーを変更テストケースIが書いた値0x45d9f3bがより良い提供混乱及び拡散を特別にすることを一つの出力ビットが変化し、すべての出力ビットに加えて、同じ確率約て互いに出力ビットの変化は(と変更された場合、入力ビットが変化した場合と同じ確率)。0x3335b369をどのように測定した方が効果的ですか?あなたにとってintは32ビットですか?
Thomas Mueller、

3
私は64ビットのunsigned intから32ビットのunsigned intまでのハッシュ関数を探しています。その場合、上記のマジックナンバーは同じですか?16ビットではなく32ビットをシフトしました。
アレッサンドロ2012年

3
その場合は、係数が大きいほど良いと思いますが、いくつかのテストを実行する必要があります。または(これが私が行うことです)最初に使用しx = ((x >> 32) ^ x)、次に上記の32ビット乗算を使用します。何が良いのか分かりません。Murmur3の64ビットファイナライザを確認
Thomas Mueller

29

データの配信方法によって異なります。単純なカウンターの場合、最も単純な関数

f(i) = i

良いでしょう(私は最適だと思いますが、証明できません)。


3
これに関する問題は、共通の要素(ワード境界のメモリアドレスなど)で割り切れる整数の大きなセットを持つことが一般的であることです。ハッシュテーブルが同じ因数で割り切れる場合、半分(または1 / 4、1 / 8など)のバケットのみが使用されることになります。
ラファウDowgird

8
@Rafal:そのため、「単純なカウンターの場合」および「データの配布方法によって異なります」という応答が
返される

5
それは実際にはjava.lang.Integerの内のメソッドのhashCodeの日()によって実装だgrepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/...
ジュアンドハシボソガラス

5
@JuandeCarrion使用されているハッシュではないため、誤解を招く可能性があります。2つのテーブルサイズの能力の使用に移行した後、Javaはから返されるすべてのハッシュを再ハッシュしますここを.hashCode()参照してください
エサイリヤ2013年

8
局所性が望ましい属性でない限り、アイデンティティ関数は、その分布特性(またはその欠如)のため、多くの実際のアプリケーションではハッシュとしてかなり役に立たない
awdz9nld

12

高速で優れたハッシュ関数は、以下のような品質の低い高速順列から構成できます。

  • 不等整数との乗算
  • バイナリ回転
  • xorshift

乱数生成用のPCGで実証されたような、優れた品質のハッシュ関数を生成するため。

これは実際、rrxmrrxmsx_0とmurmurハッシュが意図的または無意識に使用しているレシピでもあります。

個人的に見つけた

uint64_t xorshift(const uint64_t& n,int i){
  return n^(n>>i);
}
uint64_t hash(const uint64_t& n){
  uint64_t p = 0x5555555555555555ull; // pattern of alternating 0 and 1
  uint64_t c = 17316035218449499591ull;// random uneven integer constant; 
  return c*xorshift(p*xorshift(n,32),32);
}

十分に良いです。

良いハッシュ関数は

  1. 可能であれば、情報を失わないように全単射的であり、衝突が最も少ない
  2. つまり、各入力ビットはすべての出力ビットを確率0.5で反転させる必要があります。

最初に恒等関数を見てみましょう。1.を満たしていますが、2は満たしていません。

アイデンティティ関数

入力ビットnは、100%(赤)の相関を持つ出力ビットnを決定し、他の相関はありません。したがって、それらは青であり、完全な赤の線を横切っています。

xorshift(n、32)は1行半の行を生成するので、それほど優れていません。2.は2番目のアプリケーションで反転可能であるため、まだ1.を満たしています。

xorshift

符号なし整数との乗算ははるかに優れており、カスケードがより強くなり、0.5の確率でより多くの出力ビットを反転します。これは、緑色で表示されます。それは1を満たします。各不均一な整数については、乗法的な逆があります。

クヌース

2つの全単射関数を合成すると別の全単射関数が生成されるため、2つを組み合わせると次の出力が得られ、1は依然として満たされます。

knuth•xorshift

乗算とxorshiftの2番目のアプリケーションでは、次の結果が得られます。

提案されたハッシュ

または、GHashのようなガロア体乗算を使用することもできます。それらは、最近のCPUでかなり高速になり、1つのステップで優れた品質を備えています。

   uint64_t const inline gfmul(const uint64_t& i,const uint64_t& j){           
     __m128i I{};I[0]^=i;                                                          
     __m128i J{};J[0]^=j;                                                          
     __m128i M{};M[0]^=0xb000000000000000ull;                                      
     __m128i X = _mm_clmulepi64_si128(I,J,0);                                      
     __m128i A = _mm_clmulepi64_si128(X,M,0);                                      
     __m128i B = _mm_clmulepi64_si128(A,M,0);                                      
     return A[0]^A[1]^B[1]^X[0]^X[1];                                              
   }

gfmul:__m128iでは大括弧を使用できないため、コードは疑似コードのように見えます。まだ非常に興味深い。最初の行は、「ユニタライズされた__m128i(I)を取り、(パラメーター)iでxorを実行します。これを0で初期化し、iでxorとして初期化しますか?そうであれば、Iをiでロードします。そして、私に対してnot(操作)を実行しますか?
1

@Janやりたいことはですが__m128i I = i; //set the lower 64 bits、できないので使ってい^=ます。0^1 = 1したがって、含まれていません。初期化については{}、私のコンパイラを訴えたことがない、それが最善の解決策ではないかもしれませんが、私が行うことができますので、私が欲しいのは初期化すべてのそれの0であることを^=|=。私はこのコードをこのブログ投稿に基づいていると思います。これも逆転を提供し、非常に便利です:D
Wolfgang Brehm


6
  • 32ビット乗算法(非常に高速)@rafalを参照

    #define hash32(x) ((x)*2654435761)
    #define H_BITS 24 // Hashtable size
    #define H_SHIFT (32-H_BITS)
    unsigned hashtab[1<<H_BITS]  
    .... 
    unsigned slot = hash32(x) >> H_SHIFT
  • 32ビットおよび64ビット(適切なディストリビューション)はMurmurHashにあります。

  • 整数ハッシュ関数

3

Eternally Confuzzledには、いくつかのハッシュアルゴリズムの概要があります。雪崩にすぐに到達するため、効率的なハッシュテーブルルックアップに使用できるボブジェンキンスの一度に1つのハッシュをお勧めします。


4
これは良い記事ですが、整数ではなく文字列キーのハッシュに焦点を当てています。
Adrian Mouat

明確にするために、この記事のメソッドは整数に対して機能します(またはそれに適合させることができます)が、整数にはより効率的なアルゴリズムがあると思います。
Adrian Mouat

2

答えは次のような多くのものに依存します:

  • どこで採用するつもりですか?
  • ハッシュで何をしようとしていますか?
  • 暗号的に安全なハッシュ関数が必要ですか?

SHA-1などのハッシュ関数のMerkle-Damgardファミリーをご覧になることをお勧めします


1

事前にデータを知らなくても、ハッシュ関数が「良い」とは言えません。そして、あなたがそれで何をしようとしているのかを知らずに。

不明なデータサイズのハッシュテーブルよりも優れたデータ構造があります(ここでは、ハッシュテーブルのハッシュを実行していると想定しています)。限られた量のメモリに格納する必要がある要素の数が「有限」であることがわかっている場合は、個人的にハッシュテーブルを使用します。ハッシュ関数について考える前に、データの統計分析をすばやく試し、データがどのように分布しているかを確認します。


1

ランダムなハッシュ値の場合、一部のエンジニアは、黄金比の素数(2654435761)は悪い選択であると述べました。私のテスト結果では、それは真実ではないことがわかりました。代わりに、2654435761はハッシュ値を非常に適切に分散します。

#define MCR_HashTableSize 2^10

unsigned int
Hash_UInt_GRPrimeNumber(unsigned int key)
{
  key = key*2654435761 & (MCR_HashTableSize - 1)
  return key;
}

ハッシュテーブルのサイズは2の累乗でなければなりません。

多くのハッシュ関数を整数で評価するためのテストプログラムを作成しました。その結果、GRPrimeNumberが非常に適切な選択肢であることを示しています。

私が試してみました:

  1. total_data_entry_number / total_bucket_number = 2、3、4; ここで、total_bucket_number =ハッシュテーブルのサイズ;
  2. ハッシュ値ドメインをバケットインデックスドメインにマッピングします。つまり、Hash_UInt_GRPrimeNumber();に示されているように、(hash_table_size-1)を使用した論理演算でハッシュ値をバケットインデックスに変換します。
  3. 各バケットの衝突数を計算します。
  4. マッピングされていないバケット、つまり空のバケットを記録します。
  5. すべてのバケットの最大衝突数を調べます。つまり、最も長いチェーンの長さです。

私のテスト結果では、ゴールデンレシオの素数は常に空のバケットが少ないか、空のバケットがゼロであり、コリジョンチェーンの長さが最短であることがわかりました。

整数の一部のハッシュ関数は良好であると主張されていますが、テスト結果は、total_data_entry / total_bucket_number = 3の場合、最長のチェーン長が10(最大衝突数> 10)より大きく、多くのバケットがマップされない(空のバケット)ことを示しています)、これは非常に悪いです。空のバケットがゼロで、ゴールデンレシオの素数ハッシュによる最長のチェーン長が3の結果と比較すると。

ところで、私のテスト結果では、shifting-xorハッシュ関数の1つのバージョンが非常に良いことがわかりました(mikeraによって共有されています)。

unsigned int Hash_UInt_M3(unsigned int key)
{
  key ^= (key << 13);
  key ^= (key >> 17);    
  key ^= (key << 5); 
  return key;
}

2
しかし、製品を正しくシフトして、最も混合されたビットを維持するのはどうでしょうか?それはそれが機能するはずだった方法でした
ハロルド

1
@harold、黄金比の素数は慎重に選択されていますが、違いはないと思いますが、「最も混合されたビット」ではるかに優れているかどうかをテストします。私のポイントは「それは良い選択ではない」ということです。テスト結果が示すように、ビットの下部を取得するだけで十分であり、多くのハッシュ関数よりも優れています。
Chen-ChungChia

(2654435761、4295203489)は素数の黄金比です。
Chen-ChungChia

(1640565991、2654435761)も素数の黄金比です。
Chen-ChungChia

@harold、製品の右シフトが悪化します。1ポジション(2で除算)だけ右シフトしても、さらに悪化します(空のバケットはゼロのままですが、最長のチェーン長は大きくなります)。より多くの位置で右にシフトすると、結果はさらに悪化します。どうして?その理由は次のとおりです。製品を右にシフトすると、より多くのハッシュ値が互いに素にならないようになります。私の推測では、本当の理由は数論に関係しています。
Chen-ChungChia

1

このスレッドを見つけてからずっとsplitmix64(Thomas Muellerの回答で指摘されているように)使用しています。しかし、私は最近、Pelle Evensenのrrxmrrxmsx_0に遭遇しました。これは、元のMurmurHash3ファイナライザとその後継(splitmix64および他のミックス)よりも非常に優れた統計分布をもたらしました。Cのコードスニペットは次のとおりです。

#include <stdint.h>

static inline uint64_t ror64(uint64_t v, int r) {
    return (v >> r) | (v << (64 - r));
}

uint64_t rrxmrrxmsx_0(uint64_t v) {
    v ^= ror64(v, 25) ^ ror64(v, 50);
    v *= 0xA24BAED4963EE407UL;
    v ^= ror64(v, 24) ^ ror64(v, 49);
    v *= 0x9FB21C651E98DF25UL;
    return v ^ v >> 28;
}

Pelle は、最後のステップで使用された64ビットミキサーと最新のバリアントの詳細な分析も提供しますMurmurHash3


2
この関数は全単射ではありません。v = ror(v、25)のすべてのv、つまりすべて0とすべて1の場合、2か所で同じ出力が生成されます。すべての値について、v = ror64(v、24)^ ror64(v、49)、これらはv = ror(v、28)と少なくとも2つ同じであり、別の2 ^ 4を生成し、合計で約22の不要な衝突。splitmixの2つのアプリケーションは、おそらく同じくらい速く、同じくらい高速ですが、それでも可逆で、衝突はありません。
Wolfgang Brehm
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.