답변:
Knuth의 곱셈 방법 :
hash(i)=i*2654435761 mod 2^32
일반적으로 해시 크기 ( 2^32예제에서) 의 순서이고 공통 요인이없는 승수를 선택해야 합니다. 이렇게하면 해시 함수가 모든 해시 공간을 균일하게 처리합니다.
편집 :이 해시 함수의 가장 큰 단점은 분할 가능성을 유지한다는 것입니다. 따라서 정수가 모두 2 또는 4로 나눌 수있는 경우 (흔하지 않은 경우) 해시도 마찬가지입니다. 이것은 해시 테이블의 문제입니다. 사용되는 버킷의 1/2 또는 1/4 만 사용하면됩니다.
다음 알고리즘이 매우 좋은 통계 분포를 제공한다는 것을 알았습니다. 각 입력 비트는 약 50 % 확률로 각 출력 비트에 영향을줍니다. 충돌이 없습니다 (각 입력이 다른 출력을 생성 함). 알고리즘은 CPU에 내장 정수 곱셈 단위가없는 경우를 제외하고는 빠릅니다. C 코드는 가정 int32 비트 (자바 대체이다 >>으로 >>>및 삭제 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 를 사용할 때와 거의 비슷 합니다. 약간의 장점은 동일한 상수가 두 번 사용된다는 것입니다 (마지막으로 테스트했을 때 약간 더 빨라졌지만 여전히 사실인지 확실하지 않습니다).
당신은 당신이 대체하는 경우 (해시에서 입력 값을 얻을) 과정을 되돌릴 수 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 비트 숫자의 경우 가장 빠르지 않을 수도 있지만 다음을 사용하는 것이 좋습니다. 이것은 블로그 기사 Better Bit Mixing (mix 13)을 기반으로 한 것으로 보이는 splitmix64 기반입니다 .
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;
}
자바, 사용을 위해 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;
}
업데이트 : 다른 (아마도 더 나은) 상수가 나열 되는 Hash Function Prospector 프로젝트 를 살펴볼 수도 있습니다.
x = ((x >> 32) ^ x)하고 위의 32 비트 곱셈을 사용하십시오. 무엇이 더 좋은지 잘 모르겠습니다. 또한보고 할 수 있습니다 Murmur3에 대한 64 비트 종결 자
데이터가 배포되는 방식에 따라 다릅니다. 간단한 카운터의 경우 가장 간단한 기능
f(i) = i
좋을 것입니다 (최적이라고 생각하지만 증명할 수는 없습니다).
.hashCode() 참조 하십시오 .
빠르고 좋은 해시 함수는 다음과 같이 품질이 낮은 빠른 순열로 구성 될 수 있습니다.
난수 생성을 위해 PCG 로 입증 된 것과 같이 우수한 품질의 해싱 함수를 생성합니다.
이것은 사실 rrxmrrxmsx_0과 murmur hash가 고의로 또는 무의식적으로 사용하는 레시피이기도합니다.
나는 개인적으로
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);
}
충분히 좋다.
좋은 해시 함수는
먼저 identity 함수를 살펴 보겠습니다. 1은 충족하지만 2는 충족하지 않습니다. :
입력 비트 n은 100 % (빨간색)의 상관 관계로 출력 비트 n을 결정하고 나머지는 없음이므로 파란색이므로 완벽한 빨간색 선을 제공합니다.
xorshift (n, 32)는 그다지 좋지 않으며 한 줄 반을 산출합니다. 두 번째 응용 프로그램으로 뒤집을 수 있기 때문에 여전히 1. 만족합니다.
부호없는 정수를 사용한 곱셈이 훨씬 더 낫습니다. 더 강하게 계단식으로 연결되고 더 많은 출력 비트를 녹색으로 원하는 0.5 확률로 뒤집습니다. 그것은 1을 만족합니다. 각 고르지 않은 정수에 대해 곱셈 역이 있습니다.
두 가지를 결합하면 다음과 같은 결과가 나옵니다. 두 개의 bijective 함수의 구성이 다른 bijective 함수를 생성하므로 여전히 1을 만족합니다.
곱셈과 xorshift를 두 번째로 적용하면 다음이 생성됩니다.
또는 GHash 와 같은 Galois 필드 곱셈을 사용할 수 있습니다 . 최신 CPU에서 상당히 빨라 졌으며 한 단계에서 우수한 품질을 제공합니다.
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_SHIFT32 비트 및 64 비트 (좋은 배포) : MurmurHash
Eternally Confuzzled의 일부 해시 알고리즘에 대한 멋진 개요가 있습니다 . 눈사태에 빠르게 도달하므로 효율적인 해시 테이블 조회에 사용할 수있는 Bob Jenkins의 한 번에 하나씩 해시를 권장합니다.
대답은 다음과 같은 많은 것에 달려 있습니다.
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가 꽤 좋은 선택임을 보여줍니다.
나는 시도했다 :
내 테스트 결과 황금 비율 소수는 항상 빈 버킷이 적거나 빈 버킷이 0이고 충돌 체인 길이가 가장 짧다는 것을 발견했습니다.
정수에 대한 일부 해시 함수가 좋다고 주장하지만 테스트 결과는 total_data_entry / total_bucket_number = 3 일 때 가장 긴 체인 길이가 10보다 크고 (최대 충돌 수> 10) 많은 버킷이 매핑되지 않음 (빈 버킷 )는 황금 비율 소수 해싱에 의한 빈 버킷이 0이고 체인 길이가 3 인 결과와 비교하면 매우 나쁩니다.
BTW, 내 테스트 결과에서 shifting-xor 해시 함수의 한 버전이 꽤 좋다는 것을 발견했습니다 (미 케라가 공유합니다).
unsigned int Hash_UInt_M3(unsigned int key)
{
key ^= (key << 13);
key ^= (key >> 17);
key ^= (key << 5);
return key;
}
나는 이 스레드를 찾은 이래로 ( splitmix64Thomas 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.