J'ai trouvé que la fonction de hachage standard sur VS2005 est douloureusement lente lorsque vous essayez d'obtenir des recherches de haute performance. Quels sont de bons exemples d'algorithmes de hachage rapides et efficaces qui devraient annuler la plupart des collisions?
J'ai travaillé avec Paul Larson de Microsoft Research sur certaines implémentations de table de hachage. Il a étudié un certain nombre de fonctions de hachage de chaîne sur une variété d'ensembles de données et a constaté qu'une simple multiplication par 101 et une boucle d'ajout fonctionnaient étonnamment bien.
unsigned int
hash(
const char* s,
unsigned int seed = 0)
{
unsigned int hash = seed;
while (*s)
{
hash = hash * 101 + *s++;
}
return hash;
}
De mon ancien code:
/* magic numbers from http://www.isthe.com/chongo/tech/comp/fnv/ */
static const size_t InitialFNV = 2166136261U;
static const size_t FNVMultiple = 16777619;
/* Fowler / Noll / Vo (FNV) Hash */
size_t myhash(const string &s)
{
size_t hash = InitialFNV;
for(size_t i = 0; i < s.length(); i++)
{
hash = hash ^ (s[i]); /* xor the low 8 bits */
hash = hash * FNVMultiple; /* multiply by the magic number */
}
return hash;
}
C'est rapide. Freaking vraiment rapide.
Boost a une bibliothèque boost :: hash qui peut fournir quelques fonctions de hachage de base pour les types les plus courants.
Cela dépend toujours de votre ensemble de données.
Pour ma part, j'ai eu des résultats étonnamment bons en utilisant le CRC32 de la chaîne. Fonctionne très bien avec une large gamme de jeux d'entrées différents.
Beaucoup de bonnes implémentations CRC32 sont faciles à trouver sur le net.
Edit: Presque oublié: Cette page a une belle fusillade avec fonction de hachage avec des numéros de performance et des données de test:
http://smallcode.weblogs.us/ <- plus bas dans la page.
J'ai utilisé le hachage Jenkins pour écrire une bibliothèque de filtres Bloom, il a de grandes performances.
Les détails et le code sont disponibles ici: http://burtleburtle.net/bob/c/lookup3.c
C'est ce que Perl utilise pour son opération de hachage, fwiw.
Si vous hachez un ensemble fixe de mots, la meilleure fonction de hachage est souvent fonction de hachage parfaite . Cependant, ils nécessitent généralement que l'ensemble des mots que vous essayez de hacher soit connu au moment de la compilation. La détection de mots clés dans un lexer (et la traduction de mots clés en jetons) est une utilisation courante des fonctions de hachage parfaites générées avec des outils tels que gperf . Un hachage parfait vous permet également de remplacer hash_map
avec un simple tableau ou vector
.
Si vous ne hachez pas un ensemble de mots fixe, cela ne s'applique évidemment pas.
J'ai fait une petite recherche, et c'est drôle, le petit algorithme de Paul Larson est apparu ici http://www.strchr.com/hash_functions comme ayant le moins de collisions de tous testé dans un certain nombre de conditions, et c'est très rapide pour celui qu'il est déroulé ou piloté par table.
Larson est le simple multiplier par 101 et ajouter la boucle ci-dessus.
Une suggestion classique pour un hachage de chaîne consiste à parcourir les lettres une par une en ajoutant leurs valeurs ascii/unicode à un accumulateur, en multipliant chaque fois l'accumulateur par un nombre premier. (permettant un débordement sur la valeur de hachage)
template <> struct myhash{};
template <> struct myhash<string>
{
size_t operator()(string &to_hash) const
{
const char * in = to_hash.c_str();
size_t out=0;
while(NULL != *in)
{
out*= 53; //just a prime number
out+= *in;
++in;
}
return out;
}
};
hash_map<string, int, myhash<string> > my_hash_map;
Il est difficile d'aller plus vite sans jeter de données. Si vous savez que vos chaînes ne peuvent être différenciées que par quelques caractères et non par tout leur contenu, vous pouvez faire plus vite.
Vous pouvez essayer de mieux mettre en cache la valeur de hachage en créant une nouvelle sous-classe de basic_string qui se souvient de sa valeur de hachage, si la valeur est calculée trop souvent. hash_map devrait cependant le faire en interne.
De Fonctions de hachage jusqu'en bas :
MurmurHash est devenu très populaire, au moins dans les cercles des développeurs de jeux, en tant que "fonction de hachage générale".
C’est un bon choix, mais voyons plus tard si nous pouvons généralement faire mieux. Un autre bon choix, surtout si vous en savez plus sur vos données que "ça va être un nombre inconnu d'octets", est de lancer le vôtre (par exemple, voir les réponses de Won Chun, ou le xxHash/Murmur modifié de Rune qui est spécialisé pour les clés à 4 octets). etc.). Si vous connaissez vos données, essayez toujours de voir si ces connaissances peuvent être utilisées à bon escient!
Sans plus d'informations, je recommanderais MurmurHash comme un usage général fonction de hachage non cryptographique . Pour les petites chaînes (de la taille de l'identifiant moyen dans les programmes), les très simples et célèbres djb2 et FNV sont très bons.
Ici (tailles de données <10 octets), nous pouvons voir que l'intelligence ILP des autres algorithmes ne se montre pas, et la super-simplicité de FNV ou djb2 gagne en performances.
unsigned long
hash(unsigned char *str)
{
unsigned long hash = 5381;
int c;
while (c = *str++)
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
return hash;
}
hash = FNV_offset_basis
for each byte_of_data to be hashed
hash = hash × FNV_prime
hash = hash XOR byte_of_data
return hash
hash = FNV_offset_basis
for each byte_of_data to be hashed
hash = hash XOR byte_of_data
hash = hash × FNV_prime
return hash
Les fonctions de hachage peuvent rendre votre code vulnérable aux attaques par déni de service. Si un attaquant est en mesure de forcer votre serveur à gérer trop de collisions, il se peut que votre serveur ne soit pas en mesure de faire face aux demandes.
Certaines fonctions de hachage comme MurmurHash acceptent une graine que vous pouvez fournir pour réduire considérablement la capacité des attaquants à prédire les hachages générés par votre logiciel serveur. Garde cela à l'esprit.
Si vos chaînes sont en moyenne plus longues qu'une seule ligne de cache, mais que leur longueur + préfixe sont raisonnablement uniques, envisagez de n'avoir que la longueur + les 8/16 premiers caractères. (La longueur est contenue dans l'objet std :: string lui-même et donc pas chère à lire)