Réponses:
Méthode multiplicative de Knuth:
hash(i)=i*2654435761 mod 2^32
En général, vous devez choisir un multiplicateur qui est dans l'ordre de votre taille de hachage ( 2^32
dans l'exemple) et qui n'a pas de facteurs communs avec lui. De cette façon, la fonction de hachage couvre uniformément tout votre espace de hachage.
Edit: Le plus gros inconvénient de cette fonction de hachage est qu'elle préserve la divisibilité, donc si vos entiers sont tous divisibles par 2 ou par 4 (ce qui n'est pas rare), leurs hachages le seront aussi. C'est un problème dans les tables de hachage - vous pouvez vous retrouver avec seulement 1/2 ou 1/4 des seaux utilisés.
J'ai trouvé que l'algorithme suivant fournit une très bonne distribution statistique. Chaque bit d'entrée affecte chaque bit de sortie avec une probabilité d'environ 50%. Il n'y a pas de collisions (chaque entrée entraîne une sortie différente). L'algorithme est rapide sauf si le CPU n'a pas d'unité de multiplication d'entiers intégrée. Code C, en supposant qu'il int
soit 32 bits (pour Java, remplacer >>
par >>>
et supprimer unsigned
):
unsigned int hash(unsigned int x) {
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = (x >> 16) ^ x;
return x;
}
Le nombre magique a été calculé à l'aide d'un programme de test multi-thread spécial qui a duré plusieurs heures, qui calcule l'effet d'avalanche (le nombre de bits de sortie qui changent si un seul bit d'entrée est changé; devrait être proche de 16 en moyenne), indépendance de les changements de bits de sortie (les bits de sortie ne doivent pas dépendre les uns des autres), et la probabilité d'un changement dans chaque bit de sortie si un bit d'entrée est changé. Les valeurs calculées sont meilleures que le finaliseur 32 bits utilisé par MurmurHash , et presque aussi bonnes (pas tout à fait) que lors de l'utilisation d' AES . Un léger avantage est que la même constante est utilisée deux fois (cela l'a rendu légèrement plus rapide la dernière fois que j'ai testé, je ne sais pas si c'est toujours le cas).
Vous pouvez inverser le processus (obtenir la valeur d'entrée à partir du hachage) si vous remplacez le 0x45d9f3b
par 0x119de1f3
(l' inverse multiplicatif ):
unsigned int unhash(unsigned int x) {
x = ((x >> 16) ^ x) * 0x119de1f3;
x = ((x >> 16) ^ x) * 0x119de1f3;
x = (x >> 16) ^ x;
return x;
}
Pour les nombres 64 bits, je suggère d'utiliser ce qui suit, même si ce n'est peut-être pas le plus rapide. Celui-ci est basé sur splitmix64 , qui semble être basé sur l'article de blog 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;
}
Pour Java, utilisez long
, ajoutez L
à la constante, remplacez >>
par >>>
et supprimez unsigned
. Dans ce cas, l'inversion est plus compliquée:
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;
}
Mise à jour: vous pouvez également consulter le projet Hash Function Prospector , où d'autres constantes (éventuellement meilleures) sont répertoriées.
x = ((x >> 32) ^ x)
, puis utilisez les multiplications 32 bits ci-dessus. Je ne sais pas ce qui est mieux. Vous pouvez également consulter le finaliseur 64 bits pour Murmur3
Cela dépend de la façon dont vos données sont distribuées. Pour un simple compteur, la fonction la plus simple
f(i) = i
sera bon (je soupçonne optimal, mais je ne peux pas le prouver).
Les fonctions de hachage rapides et bonnes peuvent être composées de permutations rapides avec des qualités moindres, comme
Pour produire une fonction de hachage avec des qualités supérieures, comme démontré avec PCG pour la génération de nombres aléatoires.
C'est en fait aussi la recette que rrxmrrxmsx_0 et murmur hash utilisent, sciemment ou non.
J'ai personnellement trouvé
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);
}
être assez bon.
Une bonne fonction de hachage devrait
Regardons d'abord la fonction d'identité. Il satisfait 1. mais pas 2.:
Le bit d'entrée n détermine le bit de sortie n avec une corrélation de 100% (rouge) et aucune autre, ils sont donc bleus, donnant une ligne rouge parfaite.
Un xorshift (n, 32) n'est pas beaucoup mieux, donnant une ligne et demie. Toujours satisfaisant 1., car il est inversible avec une deuxième application.
Une multiplication avec un entier non signé est bien meilleure, en cascade plus fortement et en retournant plus de bits de sortie avec une probabilité de 0,5, ce que vous voulez, en vert. Il satisfait 1. comme pour chaque entier impair il y a un inverse multiplicatif.
La combinaison des deux donne le résultat suivant, toujours satisfaisant 1. car la composition de deux fonctions bijectives produit une autre fonction bijective.
Une deuxième application de multiplication et de xorshift donnera les résultats suivants:
Ou vous pouvez utiliser des multiplications de terrain Galois comme GHash , elles sont devenues raisonnablement rapides sur les processeurs modernes et ont des qualités supérieures en une seule étape.
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
, mais je ne peux pas, alors j'utilise ^=
. 0^1 = 1
donc pas non impliqué. En ce qui concerne l'initialisation avec {}
mon compilateur ne s'est jamais plaint, ce n'est peut-être pas la meilleure solution, mais ce que je veux, c'est initialiser tout à 0 pour que je puisse faire ^=
ou |=
. Je pense avoir basé ce code sur cet article de blog qui donne également l'inversion, très utile: D
Cette page répertorie quelques fonctions de hachage simples qui ont tendance à être décentes en général, mais tout hachage simple a des cas pathologiques où cela ne fonctionne pas bien.
Méthode multiplicative 32 bits (très rapide) voir @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 bits et 64 bits (bonne distribution) à: MurmurHash
Il y a un bon aperçu de certains algorithmes de hachage chez Eternally Confuzzled . Je recommanderais le hachage un par un de Bob Jenkins qui atteint rapidement l'avalanche et peut donc être utilisé pour une recherche efficace de table de hachage.
La réponse dépend de beaucoup de choses comme:
Je vous suggère de jeter un œil à la famille de fonctions de hachage Merkle-Damgard comme SHA-1, etc.
Je ne pense pas que nous puissions dire qu'une fonction de hachage est "bonne" sans connaître vos données à l'avance! et sans savoir ce que vous allez en faire.
Il existe de meilleures structures de données que les tables de hachage pour des tailles de données inconnues (je suppose que vous faites le hachage d'une table de hachage ici). J'utiliserais personnellement une table de hachage lorsque je sais que j'ai un nombre "fini" d'éléments qui doivent être stockés dans une quantité limitée de mémoire. J'essaierais de faire une analyse statistique rapide de mes données, de voir comment elles sont distribuées, etc. avant de commencer à penser à ma fonction de hachage.
Pour les valeurs de hachage aléatoires, certains ingénieurs ont dit que le nombre premier du nombre d'or (2654435761) est un mauvais choix, avec mes résultats de test, j'ai trouvé que ce n'est pas vrai; à la place, 2654435761 distribue assez bien les valeurs de hachage.
#define MCR_HashTableSize 2^10
unsigned int
Hash_UInt_GRPrimeNumber(unsigned int key)
{
key = key*2654435761 & (MCR_HashTableSize - 1)
return key;
}
La taille de la table de hachage doit être une puissance de deux.
J'ai écrit un programme de test pour évaluer de nombreuses fonctions de hachage pour les entiers, les résultats montrent que GRPrimeNumber est un très bon choix.
J'ai essayé:
Avec les résultats de mes tests, j'ai constaté que Golden Ratio Prime Number a toujours le moins de seaux vides ou zéro seau vide et la longueur de chaîne de collision la plus courte.
Certaines fonctions de hachage pour les entiers sont prétendument bonnes, mais les résultats des tests montrent que lorsque total_data_entry / total_bucket_number = 3, la longueur de chaîne la plus longue est supérieure à 10 (nombre de collisions max> 10), et de nombreux seaux ne sont pas mappés (seaux vides ), ce qui est très mauvais, comparé au résultat de zéro seau vide et de la plus longue longueur de chaîne 3 par Golden Ratio Prime Number Hashing.
BTW, avec mes résultats de test, j'ai trouvé qu'une version des fonctions de hachage shifting-xor est plutôt bonne (elle est partagée par mikera).
unsigned int Hash_UInt_M3(unsigned int key)
{
key ^= (key << 13);
key ^= (key >> 17);
key ^= (key << 5);
return key;
}
J'utilise splitmix64
(indiqué dans la réponse de Thomas Mueller ) depuis que j'ai trouvé ce fil. Cependant, je suis récemment tombé sur le rrxmrrxmsx_0 de Pelle Evensen , qui a donné une distribution statistique extrêmement meilleure que le finaliseur d'origine MurmurHash3 et ses successeurs ( splitmix64
et autres mélanges). Voici l'extrait de code en 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 fournit également une analyse en profondeur du mélangeur 64 bits utilisé dans l'étape finale de MurmurHash3
et les variantes les plus récentes.