回答:
クヌースの乗法:
hash(i)=i*2654435761 mod 2^32
一般に、ハッシュサイズのオーダ(2^32
例)にあり、それに共通の要素がない乗数を選択する必要があります。このようにして、ハッシュ関数はすべてのハッシュスペースを均一にカバーします。
編集:このハッシュ関数の最大の欠点は、分割可能性が維持されることです。そのため、整数がすべて2または4で割り切れる場合(これは珍しいことではありません)、それらのハッシュも割り切れます。これはハッシュテーブルの問題です。使用されるバケットの1/2または1/4だけになる可能性があります。
次のアルゴリズムは非常に良い統計的分布を提供することがわかりました。各入力ビットは、約50%の確率で各出力ビットに影響します。衝突はありません(各入力は異なる出力になります)。このアルゴリズムは、CPUに整数乗算ユニットが組み込まれていない場合を除いて高速です。Cコード、int
32ビットを想定(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回使用されることです(前回のテストでは、それがわずかに速くなりましたが、まだ当てはまるかどうかはわかりません)。
0x45d9f3b
を0x119de1f3
(乗法逆数)に置き換えると、プロセスを逆にする(ハッシュから入力値を取得する)ことができます。
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;
}
更新:ハッシュ関数プロスペクタープロジェクトを確認することもできます。このプロジェクトには、他の(おそらくより適切な)定数がリストされています。
x = ((x >> 32) ^ x)
、次に上記の32ビット乗算を使用します。何が良いのか分かりません。Murmur3の64ビットファイナライザを確認
データの配信方法によって異なります。単純なカウンターの場合、最も単純な関数
f(i) = i
良いでしょう(私は最適だと思いますが、証明できません)。
.hashCode()
参照してください。
高速で優れたハッシュ関数は、以下のような品質の低い高速順列から構成できます。
乱数生成用の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は満たしていません。
入力ビットnは、100%(赤)の相関を持つ出力ビットnを決定し、他の相関はありません。したがって、それらは青であり、完全な赤の線を横切っています。
xorshift(n、32)は1行半の行を生成するので、それほど優れていません。2.は2番目のアプリケーションで反転可能であるため、まだ1.を満たしています。
符号なし整数との乗算ははるかに優れており、カスケードがより強くなり、0.5の確率でより多くの出力ビットを反転します。これは、緑色で表示されます。それは1を満たします。各不均一な整数については、乗法的な逆があります。
2つの全単射関数を合成すると別の全単射関数が生成されるため、2つを組み合わせると次の出力が得られ、1は依然として満たされます。
乗算と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];
}
__m128i I = i; //set the lower 64 bits
、できないので使ってい^=
ます。0^1 = 1
したがって、含まれていません。初期化については{}
、私のコンパイラを訴えたことがない、それが最善の解決策ではないかもしれませんが、私が行うことができますので、私が欲しいのは初期化すべてのそれの0であることを^=
か|=
。私はこのコードをこのブログ投稿に基づいていると思います。これも逆転を提供し、非常に便利です:D
このページには、一般的にまともな傾向があるいくつかの単純なハッシュ関数がリストされていますが、単純なハッシュには、うまく機能しない病理学的なケースがあります。
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にあります。
Eternally Confuzzledには、いくつかのハッシュアルゴリズムの概要があります。雪崩にすぐに到達するため、効率的なハッシュテーブルルックアップに使用できるボブジェンキンスの一度に1つのハッシュをお勧めします。
答えは次のような多くのものに依存します:
SHA-1などのハッシュ関数のMerkle-Damgardファミリーをご覧になることをお勧めします
ランダムなハッシュ値の場合、一部のエンジニアは、黄金比の素数(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が非常に適切な選択肢であることを示しています。
私が試してみました:
私のテスト結果では、ゴールデンレシオの素数は常に空のバケットが少ないか、空のバケットがゼロであり、コリジョンチェーンの長さが最短であることがわかりました。
整数の一部のハッシュ関数は良好であると主張されていますが、テスト結果は、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;
}
このスレッドを見つけてからずっと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
。