J'ai testé différents algorithmes, mesurant la vitesse et le nombre de collisions.
J'ai utilisé trois jeux de clés différents:
Pour chaque corpus, le nombre de collisions et le temps moyen de hachage ont été enregistrés.
J'ai testé:
Résultats
Chaque résultat contient le temps de hachage moyen et le nombre de collisions
Hash Lowercase Random UUID Numbers
============= ============= =========== ==============
Murmur 145 ns 259 ns 92 ns
6 collis 5 collis 0 collis
FNV-1a 152 ns 504 ns 86 ns
4 collis 4 collis 0 collis
FNV-1 184 ns 730 ns 92 ns
1 collis 5 collis 0 collis▪
DBJ2a 158 ns 443 ns 91 ns
5 collis 6 collis 0 collis▪▪▪
DJB2 156 ns 437 ns 93 ns
7 collis 6 collis 0 collis▪▪▪
SDBM 148 ns 484 ns 90 ns
4 collis 6 collis 0 collis**
SuperFastHash 164 ns 344 ns 118 ns
85 collis 4 collis 18742 collis
CRC32 250 ns 946 ns 130 ns
2 collis 0 collis 0 collis
LoseLose 338 ns - -
215178 collis
Notes :
Les collisions se produisent-elles réellement?
Oui. J'ai commencé à écrire mon programme de test pour voir si les collisions de hachage se produisaient réellement - et ne constituaient pas simplement un concept théorique. Ils se produisent effectivement:
Collisions FNV-1
creamwove
entre en collision avec quists
Collisions FNV-1a
costarring
entre en collision avec liquid
declinate
entre en collision avec macallums
altarage
entre en collision avec zinke
altarages
entre en collision avec zinkes
Collisions Murmur2
cataract
entre en collision avec periti
roquette
entre en collision avec skivie
shawl
entre en collision avec stormbound
dowlases
entre en collision avec tramontane
cricketings
entre en collision avec twanger
longans
entre en collision avec whigs
Collisions DJB2
hetairas
entre en collision avec mentioner
heliotropes
entre en collision avec neurospora
depravement
entre en collision avec serafins
stylist
entre en collision avec subgenera
joyful
entre en collision avec synaphea
redescribed
entre en collision avec urites
dram
entre en collision avec vivency
Collisions DJB2a
haggadot
entre en collision avec loathsomenesses
adorablenesses
entre en collision avec rentability
playwright
entre en collision avec snush
playwrighting
entre en collision avec snushing
treponematoses
entre en collision avec waterbeds
Collisions CRC32
codding
entre en collision avec gnu
exhibiters
entre en collision avec schlager
Collisions SuperFastHash
dahabiah
entre en collision avec drapability
encharm
entre en collision avec enclave
grahams
entre en collision avec gramary
- ... coupez 79 collisions ...
night
entre en collision avec vigil
nights
entre en collision avec vigils
finks
entre en collision avec vinic
Aléatoire
L’autre mesure subjective est la distribution aléatoire des hachages. Le mappage des tables de hachage obtenues montre comment les données sont distribuées. Toutes les fonctions de hachage montrent une bonne distribution lors du mappage linéaire de la table:
Ou comme une carte de Hilbert ( XKCD est toujours pertinent ):
Sauf lorsque les chaînes numériques (hashing "1"
, "2"
, ..., "216553"
) (par exemple, des codes postaux ), où commencent à émerger des modèles dans la plupart des algorithmes de hachage:
SDBM :
DJB2a :
FNV-1 :
Tous sauf FNV-1a , qui me semble toujours assez aléatoire:
En fait, Murmur2 semble avoir encore mieux son caractère aléatoire avec Numbers
que FNV-1a
:
Quand je regarde la FNV-1a
carte "nombre", je pense voir des motifs verticaux subtils. Avec Murmur, je ne vois aucune tendance. Qu'est-ce que tu penses?
Le supplément *
dans le tableau indique à quel point le caractère aléatoire est mauvais. Avec FNV-1a
être le meilleur, et DJB2x
étant le pire:
Murmur2: .
FNV-1a: .
FNV-1: ▪
DJB2: ▪▪
DJB2a: ▪▪
SDBM: ▪▪▪
SuperFastHash: .
CRC: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
Loselose: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪
▪▪▪▪▪▪▪▪▪▪▪▪▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
Au départ, j’avais écrit ce programme pour décider si je devais même me soucier des collisions: c’est le cas.
Et ensuite, il s’est assuré que les fonctions de hachage étaient suffisamment aléatoires.
Algorithme FNV-1a
Le hachage FNV1 est proposé dans des variantes qui renvoient des hachages de 32, 64, 128, 256, 512 et 1024 bits.
L' algorithme FNV-1a est:
hash = FNV_offset_basis
for each octetOfData to be hashed
hash = hash xor octetOfData
hash = hash * FNV_prime
return hash
Où les constantes FNV_offset_basis
et FNV_prime
dépendent de la taille de hachage de retour souhaitée:
Hash Size
===========
32-bit
prime: 2^24 + 2^8 + 0x93 = 16777619
offset: 2166136261
64-bit
prime: 2^40 + 2^8 + 0xb3 = 1099511628211
offset: 14695981039346656037
128-bit
prime: 2^88 + 2^8 + 0x3b = 309485009821345068724781371
offset: 144066263297769815596495629667062367629
256-bit
prime: 2^168 + 2^8 + 0x63 = 374144419156711147060143317175368453031918731002211
offset: 100029257958052580907070968620625704837092796014241193945225284501741471925557
512-bit
prime: 2^344 + 2^8 + 0x57 = 35835915874844867368919076489095108449946327955754392558399825615420669938882575126094039892345713852759
offset: 9659303129496669498009435400716310466090418745672637896108374329434462657994582932197716438449813051892206539805784495328239340083876191928701583869517785
1024-bit
prime: 2^680 + 2^8 + 0x8d = 5016456510113118655434598811035278955030765345404790744303017523831112055108147451509157692220295382716162651878526895249385292291816524375083746691371804094271873160484737966720260389217684476157468082573
offset: 1419779506494762106872207064140321832088062279544193396087847491461758272325229673230371772250864096521202355549365628174669108571814760471015076148029755969804077320157692458563003215304957150157403644460363550505412711285966361610267868082893823963790439336411086884584107735010676915
Voir la page principale FNV pour plus de détails.
Tous mes résultats sont avec la variante 32 bits.
FNV-1 meilleur que FNV-1a?
FNV-1a est tout à fait mieux. Il y avait plus de collisions avec FNV-1a en utilisant le mot anglais corpus:
Hash Word Collisions
====== ===============
FNV-1 1
FNV-1a 4
Maintenant, comparez les minuscules et les majuscules:
Hash lowercase word Collisions UPPERCASE word collisions
====== ========================= =========================
FNV-1 1 9
FNV-1a 4 11
Dans ce cas, FNV-1a n'est pas "400%" pire que FN-1, seulement 20% pire.
Je pense que le point le plus important à retenir est qu’il existe deux classes d’algorithmes en matière de collision:
- collisions rares : FNV-1, FNV-1a, DJB2, DJB2a, SDBM
- collisions communes : SuperFastHash, Loselose
Et puis il y a la façon dont les hachages sont distribués uniformément:
- distribution exceptionnelle: Murmur2, FNV-1a, SuperFastHas
- excellente distribution: FNV-1
- bonne distribution: SDBM, DJB2, DJB2a
- distribution horrible: Loselose
Mise à jour
Murmure? Bien sûr, pourquoi pas
Mise à jour
@whatshisname s'est demandé comment se comporterait un CRC32 , ajoutait des chiffres à la table.
CRC32 est très bon . Peu de collisions, mais plus lentes, et les frais généraux d’une table de recherche 1k.
Snip tous les trucs erronés sur la distribution du CRC - mon mauvais
Jusqu'à aujourd'hui, j'allais utiliser FNV-1a comme algorithme de hachage de facto de table de hachage. Mais maintenant je passe à Murmur2:
- plus rapide
- Meilleure randomisation de toutes les classes d'entrées
Et j'espère vraiment qu'il y a quelque chose qui ne va pas avec l' SuperFastHash
algorithme que j'ai trouvé ; c'est dommage d'être aussi populaire que ça.
Mise à jour: depuis la page d'accueil MurmurHash3 sur Google :
(1) - SuperFastHash a de très mauvaises propriétés de collision, qui ont été documentées ailleurs.
Donc je suppose que ce n'est pas juste moi.
Mise à jour: j'ai compris pourquoi Murmur
c'est plus rapide que les autres. MurmurHash2 fonctionne sur quatre octets à la fois. La plupart des algorithmes sont octets par octets :
for each octet in Key
AddTheOctetToTheHash
Cela signifie que lorsque les touches s'allongent, Murmur a la chance de briller.
Mise à jour
Dans un article opportun de Raymond Chen, on réitère le fait que les GUID "aléatoires" ne sont pas destinés à être utilisés pour leur caractère aléatoire. Ils, ou un sous-ensemble d’entre eux, ne conviennent pas comme clé de hachage:
Même l’algorithme GUID de la version 4 n’est pas toujours imprévisible, car il ne spécifie pas la qualité du générateur de nombres aléatoires. L'article de Wikipedia pour GUID contient des recherches principales qui suggèrent que les GUID futurs et antérieurs peuvent être prédits sur la base de la connaissance de l'état du générateur de nombres aléatoires, car le générateur n'est pas cryptographiquement puissant.
Le hasard n'est pas la même chose que d'éviter les collisions; C'est pourquoi ce serait une erreur d'essayer d'inventer votre propre algorithme de "hachage" en prenant un sous-ensemble d'un guid "aléatoire":
int HashKeyFromGuid(Guid type4uuid)
{
//A "4" is put somewhere in the GUID.
//I can't remember exactly where, but it doesn't matter for
//the illustrative purposes of this pseudocode
int guidVersion = ((type4uuid.D3 & 0x0f00) >> 8);
Assert(guidVersion == 4);
return (int)GetFirstFourBytesOfGuid(type4uuid);
}
Note : Encore une fois, je mets "GUID aléatoire" entre guillemets, car c'est la variante "aléatoire" des GUID. Une description plus précise serait Type 4 UUID
. Mais personne ne sait ce que sont le type 4 ou les types 1, 3 et 5. Il est donc plus simple de les appeler des GUID "aléatoires".
Tous les mots anglais miroirs