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 Numbers
di FNV-1a
:
Quando guardo la FNV-1a
mappa 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-1a
essere il migliore e DJB2x
il 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_basis
e FNV_prime
dipendono 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 SuperFastHash
nell'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