Ho testato alcuni algoritmi diversi, misurando la velocità e il numero di collisioni.
Ho usato tre diversi set di chiavi:
Per ciascun corpus, è stato registrato il numero di collisioni e il tempo medio impiegato per l'hash.
Ho testato:
risultati
Ogni risultato contiene il tempo di hash medio e il numero di collisioni
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
Note :
Le collisioni avvengono effettivamente?
Sì. Ho iniziato a scrivere il mio programma di test per vedere se si verificano effettivamente collisioni di hash e non sono solo un costrutto teorico. Succedono davvero:
Collisioni FNV-1
creamwove si scontra con quists
Collisioni FNV-1a
costarring si scontra con liquid
declinate si scontra con macallums
altarage si scontra con zinke
altarages si scontra con zinkes
Collisioni Murmur2
cataract si scontra con periti
roquette si scontra con skivie
shawl si scontra con stormbound
dowlases si scontra con tramontane
cricketings si scontra con twanger
longans si scontra con whigs
Collisioni DJB2
hetairas si scontra con mentioner
heliotropes si scontra con neurospora
depravement si scontra con serafins
stylist si scontra con subgenera
joyful si scontra con synaphea
redescribed si scontra con urites
dram si scontra con vivency
Collisioni DJB2a
haggadot si scontra con loathsomenesses
adorablenesses si scontra con rentability
playwright si scontra con snush
playwrighting si scontra con snushing
treponematoses si scontra con waterbeds
Collisioni CRC32
codding si scontra con gnu
exhibiters si scontra con schlager
Collisioni SuperFastHash
dahabiah si scontra con drapability
encharm si scontra con enclave
grahams si scontra con gramary
- ... taglia 79 collisioni ...
night si scontra con vigil
nights si scontra con vigils
finks si scontra con vinic
Randomnessification
L'altra misura soggettiva è la distribuzione casuale degli hash. La mappatura delle tabelle hash risultanti mostra la distribuzione uniforme dei dati. Tutte le funzioni hash mostrano una buona distribuzione quando si mappa la tabella in modo lineare:

O come una mappa di Hilbert ( XKCD è sempre rilevante ):

Tranne quando hashing stringhe numerici ( "1", "2", ..., "216553") (per esempio, i codici di avviamento postale ), in cui i modelli cominciano ad emergere nella maggior parte degli algoritmi di hashing:
SDBM :

DJB2a :

FNV-1 :

Tutti tranne FNV-1a , che mi sembrano ancora abbastanza casuali:

In effetti, Murmur2 sembra avere una casualità persino migliore Numbersdi FNV-1a:

Quando guardo la FNV-1amappa dei "numeri", penso di vedere sottili schemi verticali. Con Murmur non vedo affatto schemi. Cosa ne pensi?
Gli extra *nella tabella indicano quanto sia grave la casualità. Con l' FNV-1aessere il migliore e DJB2xil peggio:
Murmur2: .
FNV-1a: .
FNV-1: ▪
DJB2: ▪▪
DJB2a: ▪▪
SDBM: ▪▪▪
SuperFastHash: .
CRC: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
Loselose: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪
▪▪▪▪▪▪▪▪▪▪▪▪▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
Inizialmente avevo scritto questo programma per decidere se dovevo preoccuparmi delle collisioni: lo faccio.
E poi si è verificato che le funzioni hash fossero sufficientemente casuali.
Algoritmo FNV-1a
L'hash FNV1 è disponibile in varianti che restituiscono hash a 32, 64, 128, 256, 512 e 1024 bit.
L' algoritmo FNV-1a è:
hash = FNV_offset_basis
for each octetOfData to be hashed
hash = hash xor octetOfData
hash = hash * FNV_prime
return hash
Dove le costanti FNV_offset_basise FNV_primedipendono dalla dimensione dell'hash di ritorno che si desidera:
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
Vedi la pagina principale di FNV per i dettagli.
Tutti i miei risultati sono con la variante a 32 bit.
FNV-1 meglio di FNV-1a?
No. FNV-1a è tutto meglio. Ci sono state più collisioni con FNV-1a quando si utilizzava la parola inglese corpus:
Hash Word Collisions
====== ===============
FNV-1 1
FNV-1a 4
Ora confronta lettere minuscole e maiuscole:
Hash lowercase word Collisions UPPERCASE word collisions
====== ========================= =========================
FNV-1 1 9
FNV-1a 4 11
In questo caso l'FNV-1a non è "400%" peggiore dell'FN-1, solo il 20% peggiore.
Penso che la cosa più importante sia che ci sono due classi di algoritmi quando si tratta di collisioni:
- collisioni rare : FNV-1, FNV-1a, DJB2, DJB2a, SDBM
- collisioni comuni : SuperFastHash, Loselose
E poi c'è la distribuzione uniforme degli hash:
- distribuzione eccezionale: Murmur2, FNV-1a, SuperFastHas
- ottima distribuzione: FNV-1
- buona distribuzione: SDBM, DJB2, DJB2a
- orribile distribuzione: Loselose
Aggiornare
Mormorio? Certo, perché no
Aggiornare
@whatshisname si chiedeva come avrebbe funzionato un CRC32 , aggiungendo numeri alla tabella.
CRC32 è abbastanza buono . Poche collisioni, ma più lente, e il sovraccarico di una tabella di ricerca 1k.
Taglia tutte le cose errate sulla distribuzione CRC - il mio male
Fino ad oggi stavo per usare FNV-1a come il mio , di fatto, algoritmo di hash hash-table. Ma ora sto passando a Murmur2:
- Più veloce
- Migliore casualità di tutte le classi di input
E davvero, spero davvero che ci sia qualcosa che non va SuperFastHashnell'algoritmo che ho trovato ; è un peccato essere così popolare come è.
Aggiornamento: dalla home page MurmurHash3 su Google :
(1) - SuperFastHash ha proprietà di collisione molto scarse, che sono state documentate altrove.
Quindi suppongo che non sono solo io.
Aggiornamento: ho capito perché Murmurè più veloce degli altri. MurmurHash2 funziona su quattro byte alla volta. La maggior parte degli algoritmi sono byte per byte :
for each octet in Key
AddTheOctetToTheHash
Ciò significa che man mano che le chiavi si allungano, il soffio ha la possibilità di brillare.
Aggiornare
Un post tempestivo di Raymond Chen ribadisce il fatto che i GUID "casuali" non sono pensati per essere utilizzati per la loro casualità. Loro, o un loro sottoinsieme, non sono adatti come chiave hash:
Anche l'algoritmo GUID versione 4 non è garantito come imprevedibile, poiché l'algoritmo non specifica la qualità del generatore di numeri casuali. L'articolo di Wikipedia per GUID contiene ricerche primarie che suggeriscono che i GUID futuri e precedenti possono essere previsti in base alla conoscenza dello stato del generatore di numeri casuali, poiché il generatore non è crittograficamente forte.
La casualità non è la stessa come evitare le collisioni; ed è per questo che sarebbe un errore provare a inventare il proprio algoritmo di "hashing" prendendo un sottoinsieme di una guida "casuale":
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);
}
Nota : ancora una volta, ho inserito il "GUID casuale" tra virgolette, perché è la variante "casuale" dei GUID. Una descrizione più accurata sarebbe Type 4 UUID. Ma nessuno sa cosa siano i tipi 4 o 1, 3 e 5. Quindi è più semplice chiamarli GUID "casuali".
Tutti gli specchi di parole inglesi