정수 해시 키를 허용하는 정수 해시 함수는 무엇입니까?


답변:


47

Knuth의 곱셈 방법 :

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

일반적으로 해시 크기 ( 2^32예제에서) 의 순서이고 공통 요인이없는 승수를 선택해야 합니다. 이렇게하면 해시 함수가 모든 해시 공간을 균일하게 처리합니다.

편집 :이 해시 함수의 가장 큰 단점은 분할 가능성을 유지한다는 것입니다. 따라서 정수가 모두 2 또는 4로 나눌 수있는 경우 (흔하지 않은 경우) 해시도 마찬가지입니다. 이것은 해시 테이블의 문제입니다. 사용되는 버킷의 1/2 또는 1/4 만 사용하면됩니다.


36
유명한 이름에 붙어 있지만 정말 나쁜 해시 함수입니다.
Seun Osewa 2010 년

5
프라임 테이블 크기와 함께 사용하면 해시 함수가 전혀 나쁘지 않습니다. 또한 닫힌 해싱을 의미합니다 . 해시 값이 균일하게 분산되지 않은 경우 곱셈 해싱은 한 값의 충돌이 다른 해시 값과 항목을 "방해"하지 않도록합니다.
Paolo Bonzini 2011 년

11
궁금한 사람을 위해이 상수는 해시 크기 (2 ^ 32)를 Phi로 나눈 값으로 선택됩니다
awdz9nld

7
Paolo : Knuth의 방법은 상위 비트에서 눈사태가 발생하지 않는다는 점에서 "나쁜"것입니다
awdz9nld

9
자세히 살펴보면 2654435761이 실제로 소수입니다. 이 2654435769.보다는 선택되었다 그 이유는 아마 그래서
karadoc

149

다음 알고리즘이 매우 좋은 통계 분포를 제공한다는 것을 알았습니다. 각 입력 비트는 약 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 프로젝트 를 살펴볼 수도 있습니다.


2
처음 두 줄은 정확히 동일합니다! 여기에 오타가 있습니까?
Kshitij Banerjee 2012

3
아니오 이것은 오타가 아니며 두 번째 줄은 비트를 더 혼합합니다. 하나의 곱셈 만 사용하는 것은 좋지 않습니다.
Thomas Mueller 2012

3
A에 따라 때문에 마법 번호를 변경 테스트 케이스 I 쓴 값 0x45d9f3b 더 제공 혼란 확산 특별히 것을 하나 개의 출력 비트 변화 모두 출력 비트 이외에 동일한 확률에 대해 각각 다른 출력 비트 변경합니다 (로 변경하면 입력 비트가 변경되면 동일한 확률). 0x3335b369를 어떻게 측정 하셨나요? 당신을 위해 int 32 비트입니까?
Thomas Mueller 2012

3
64 비트 unsigned int에서 32 비트 unsigned int에 대한 멋진 해시 함수를 찾고 있습니다. 이 경우 위의 매직 넘버는 동일합니까? 16 비트 대신 32 비트를 이동했습니다.
alessandro

3
나는 그 경우 더 큰 요인이 더 좋을 것이라고 믿지만 몇 가지 테스트를 실행해야 할 것입니다. 또는 (이것은 내가하는 일입니다) 먼저 사용 x = ((x >> 32) ^ x)하고 위의 32 비트 곱셈을 사용하십시오. 무엇이 더 좋은지 잘 모르겠습니다. 또한보고 할 수 있습니다 Murmur3에 대한 64 비트 종결 자
토마스 뮐러에게

29

데이터가 배포되는 방식에 따라 다릅니다. 간단한 카운터의 경우 가장 간단한 기능

f(i) = i

좋을 것입니다 (최적이라고 생각하지만 증명할 수는 없습니다).


3
이것의 문제는 공통 요소 (단어 정렬 메모리 주소 등)로 나눌 수있는 큰 정수 세트를 갖는 것이 일반적이라는 것입니다. 이제 해시 테이블이 동일한 요소로 나눌 수있는 경우에는 절반 (또는 1/4, 1/8 등) 버킷 만 사용됩니다.
Rafał Dowgird

8
@Rafal : 그래서 응답에 "간단한 카운터"및 "데이터 배포 방식에 따라 다름"이라고 표시됩니다.
erikkallen

5
즉 실제로 때 java.lang.Integer의 메소드 해시 코드의 일 ()에 의해 구현의 grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/...
후안 카리

5
@JuandeCarrion 사용중인 해시가 아니기 때문에 오해의 소지가 있습니다. 두 개의 테이블 크기를 사용하는 것으로 이동 한 후 Java는에서 반환 된 모든 해시를 다시 해시 합니다 . 여기를.hashCode() 참조 하십시오 .
Esailija 2013 년

8
물론, locality가 원하는 속성이 아닌 한, identity 함수는 분산 속성 (또는 그것의 부족)으로 인해 많은 실제 애플리케이션에서 해시로 상당히 쓸모가 없습니다
awdz9nld

12

빠르고 좋은 해시 함수는 다음과 같이 품질이 낮은 빠른 순열로 구성 될 수 있습니다.

  • 고르지 않은 정수로 곱하기
  • 이진 회전
  • xorshift

난수 생성을 위해 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);
}

충분히 좋다.

좋은 해시 함수는

  1. 가능하면 정보를 잃어 버리지 않기 위해 투사 적이어야하며 충돌을 최소화합니다.
  2. 가능한 한 많이 그리고 균등하게 캐스케이드합니다. 즉, 각 입력 비트는 확률 0.5로 모든 출력 비트를 뒤집어 야합니다.

먼저 identity 함수를 살펴 보겠습니다. 1은 충족하지만 2는 충족하지 않습니다. :

신원 기능

입력 비트 n은 100 % (빨간색)의 상관 관계로 출력 비트 n을 결정하고 나머지는 없음이므로 파란색이므로 완벽한 빨간색 선을 제공합니다.

xorshift (n, 32)는 그다지 좋지 않으며 한 줄 반을 산출합니다. 두 번째 응용 프로그램으로 뒤집을 수 있기 때문에 여전히 1. 만족합니다.

xorshift

부호없는 정수를 사용한 곱셈이 훨씬 더 낫습니다. 더 강하게 계단식으로 연결되고 더 많은 출력 비트를 녹색으로 원하는 0.5 확률로 뒤집습니다. 그것은 1을 만족합니다. 각 고르지 않은 정수에 대해 곱셈 역이 있습니다.

knuth

두 가지를 결합하면 다음과 같은 결과가 나옵니다. 두 개의 bijective 함수의 구성이 다른 bijective 함수를 생성하므로 여전히 1을 만족합니다.

knuth • xorshift

곱셈과 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];                                              
   }

gfmul : 코드는 의사 코드로 보입니다. __m128i에는 대괄호를 사용할 수 없습니다. 여전히 매우 흥미 롭습니다. 첫 번째 줄은 "통일화 된 __m128i (I)를 사용하고 (매개 변수) i로 xor합니다. 이것을 0으로 초기화하고 i로 xor로 읽어야합니까? 그렇다면 i를 사용하여로드 I와 동일할까요? I에 대해 not (작업)을 수행합니까?
1

@Jan 내가 원하는 것은 __m128i I = i; //set the lower 64 bits이지만 할 수 없으므로 ^=. 0^1 = 1그러므로 관련이 없습니다. 와 초기화에 대해서는 {}결코 불평하지 내 컴파일러 것은, 그것은 최고의 솔루션이 아닐 수도 있지만, 내가 할 수있는, 그래서 내가 그와 함께 원하는 것은 0으로 모든 초기화이다 ^=|=. 나는 이 블로그 포스트 에 그 코드를 기반으로했다고 생각합니다. 이것은 매우 유용합니다. : D
Wolfgang Brehm

6

이 페이지 에는 일반적으로 괜찮은 경향이있는 간단한 해시 함수가 나열되어 있지만 모든 간단한 해시는 제대로 작동하지 않는 병리학적인 경우가 있습니다.


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의 일부 해시 알고리즘에 대한 멋진 개요가 있습니다 . 눈사태에 빠르게 도달하므로 효율적인 해시 테이블 조회에 사용할 수있는 Bob Jenkins의 한 번에 하나씩 해시를 권장합니다.


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)을 사용하여 Logical And Operation에 의해 해시 값을 버킷 인덱스로 변환합니다.
  3. 각 버킷의 충돌 수를 계산하십시오.
  4. 매핑되지 않은 버킷, 즉 빈 버킷을 기록합니다.
  5. 모든 버킷의 최대 충돌 수를 찾으십시오. 즉, 가장 긴 체인 길이입니다.

내 테스트 결과 황금 비율 소수는 항상 빈 버킷이 적거나 빈 버킷이 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;
}

2
그렇다면 제품을 올바르게 전환하여 가장 많이 혼합 된 비트를 유지하는 것은 어떻습니까? 그것이 작동하는 방식이었습니다
harold

1
@harold, 황금 비율 소수는 신중하게 선택되었지만 아무런 차이가 없을 것이라고 생각하지만 "가장 많이 혼합 된 비트"로 훨씬 더 나은지 테스트 할 것입니다. 내 요점은 "좋은 선택이 아닙니다."라는 것입니다. 테스트 결과에서 알 수 있듯이 비트의 아래쪽 부분을 잡는 것만으로도 충분하고 많은 해시 함수보다 낫습니다.
Chen-ChungChia

(2654435761, 4295203489)는 소수의 황금 비율입니다.
Chen-ChungChia

(1640565991, 2654435761)은 소수의 황금 비율이기도합니다.
Chen-ChungChia

@harold, 제품을 오른쪽으로 이동하는 것은 더 나빠집니다. 오른쪽으로 1 위치 (2로 나눈 값) 만 이동하더라도 여전히 악화됩니다 (여전히 빈 버킷이 0이지만 가장 긴 체인 길이가 더 큽니다). 더 많은 위치로 오른쪽으로 이동하면 결과가 더 나빠집니다. 왜? 그 이유는 제품을 올바르게 이동하면 더 많은 해시 값이 코 프라임이되지 않도록하는 것입니다. 내 추측에 진짜 이유는 수 이론과 관련이 있습니다.
Chen-ChungChia

1

나는 이 스레드를 찾은 이래로 ( 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.


2
이 함수는 bijective가 아닙니다. v = ror (v, 25) 인 모든 v, 즉 모두 0과 모두 1에 대해 두 위치에서 동일한 출력을 생성합니다. 모든 값에 대해 v = ror64 (v, 24) ^ ror64 (v, 49), 최소 2 개 이상이고 v = ror (v, 28)과 동일하여 2 ^ 4가 추가되어 약 22 개의 불필요한 충돌이 발생합니다. . splitmix의 두 가지 응용 프로그램은 아마도 훌륭하고 빠르지 만 여전히 반전 가능하고 충돌이 없습니다.
Wolfgang Brehm
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.