Ich habe verschiedene Algorithmen getestet, die Geschwindigkeit und die Anzahl der Kollisionen messen.
Ich habe drei verschiedene Schlüsselsätze verwendet:
Für jeden Korpus wurde die Anzahl der Kollisionen und die durchschnittliche Zeit, die für das Hashing aufgewendet wurde, aufgezeichnet.
Ich habe getestet:
Ergebnisse
Jedes Ergebnis enthält die durchschnittliche Hash-Zeit und die Anzahl der Kollisionen
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
Anmerkungen :
Treten tatsächlich Kollisionen auf?
Ja. Ich habe angefangen, mein Testprogramm zu schreiben, um festzustellen, ob tatsächlich Hash-Kollisionen auftreten - und das ist nicht nur ein theoretisches Konstrukt. Sie passieren tatsächlich:
FNV-1-Kollisionen
creamwove
kollidiert mit quists
FNV-1a-Kollisionen
costarring
kollidiert mit liquid
declinate
kollidiert mit macallums
altarage
kollidiert mit zinke
altarages
kollidiert mit zinkes
Murmel2-Kollisionen
cataract
kollidiert mit periti
roquette
kollidiert mit skivie
shawl
kollidiert mit stormbound
dowlases
kollidiert mit tramontane
cricketings
kollidiert mit twanger
longans
kollidiert mit whigs
DJB2-Kollisionen
hetairas
kollidiert mit mentioner
heliotropes
kollidiert mit neurospora
depravement
kollidiert mit serafins
stylist
kollidiert mit subgenera
joyful
kollidiert mit synaphea
redescribed
kollidiert mit urites
dram
kollidiert mit vivency
DJB2a Kollisionen
haggadot
kollidiert mit loathsomenesses
adorablenesses
kollidiert mit rentability
playwright
kollidiert mit snush
playwrighting
kollidiert mit snushing
treponematoses
kollidiert mit waterbeds
CRC32-Kollisionen
codding
kollidiert mit gnu
exhibiters
kollidiert mit schlager
SuperFastHash-Kollisionen
dahabiah
kollidiert mit drapability
encharm
kollidiert mit enclave
grahams
kollidiert mit gramary
- ... 79 Kollisionen ausschneiden ...
night
kollidiert mit vigil
nights
kollidiert mit vigils
finks
kollidiert mit vinic
Randomnessification
Das andere subjektive Maß ist, wie zufällig die Hashes verteilt sind. Die Zuordnung der resultierenden HashTables zeigt, wie gleichmäßig die Daten verteilt sind. Alle Hash-Funktionen zeigen eine gute Verteilung, wenn die Tabelle linear abgebildet wird:
Oder als Hilbert Map ( XKCD ist immer relevant ):
Außer , wenn Hashing Zahlenketten ( "1"
, "2"
..., "216553"
) (zB Postleitzahlen ), wo Muster beginnen in den meisten der Hash - Algorithmen entstehen:
SDBM :
DJB2a :
FNV-1 :
Alle außer FNV-1a , die für mich immer noch ziemlich zufällig aussehen:
Tatsächlich scheint Murmur2 eine noch bessere Zufälligkeit zu haben Numbers
als FNV-1a
:
Als ich im Blick FNV-1a
„Nummer“ Karte, ich glaube , ich subtile vertikale Muster zu sehen. Mit Murmeln sehe ich überhaupt keine Muster. Was denkst du?
Das Extra *
in der Tabelle gibt an, wie schlecht die Zufälligkeit ist. Mit FNV-1a
der Beste zu sein, und DJB2x
ist das Schlimmste:
Murmur2: .
FNV-1a: .
FNV-1: ▪
DJB2: ▪▪
DJB2a: ▪▪
SDBM: ▪▪▪
SuperFastHash: .
CRC: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
Loselose: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪
▪▪▪▪▪▪▪▪▪▪▪▪▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
Ich habe dieses Programm ursprünglich geschrieben, um zu entscheiden, ob ich mir überhaupt Gedanken über Kollisionen machen musste: Ich tue es.
Und dann wurde sichergestellt, dass die Hash-Funktionen ausreichend zufällig waren.
FNV-1a-Algorithmus
Der FNV1-Hash gibt es in Varianten, die 32-, 64-, 128-, 256-, 512- und 1024-Bit-Hashes zurückgeben.
Der FNV-1a-Algorithmus lautet:
hash = FNV_offset_basis
for each octetOfData to be hashed
hash = hash xor octetOfData
hash = hash * FNV_prime
return hash
Wo die Konstanten FNV_offset_basis
und FNV_prime
von der gewünschten Rückgabe-Hash-Größe abhängen:
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
Einzelheiten finden Sie auf der FNV-Hauptseite .
Alle meine Ergebnisse sind mit der 32-Bit-Variante.
FNV-1 besser als FNV-1a?
Nein, FNV-1a ist rundum besser. Es gab mehr Kollisionen mit FNV-1a, wenn das englische Wort Corpus verwendet wurde:
Hash Word Collisions
====== ===============
FNV-1 1
FNV-1a 4
Vergleichen Sie nun Klein- und Großbuchstaben:
Hash lowercase word Collisions UPPERCASE word collisions
====== ========================= =========================
FNV-1 1 9
FNV-1a 4 11
In diesem Fall ist FNV-1a nicht "400%" schlechter als FN-1, sondern nur 20% schlechter.
Ich denke, der wichtigste Aspekt ist, dass es zwei Klassen von Algorithmen gibt, wenn es um Kollisionen geht:
- Kollisionen selten : FNV-1, FNV-1a, DJB2, DJB2a, SDBM
- Kollisionen häufig : SuperFastHash, Loselose
Und dann ist da noch die gleichmäßige Verteilung der Hashes:
- Hervorragende Verbreitung: Murmur2, FNV-1a, SuperFastHas
- ausgezeichnete Verteilung: FNV-1
- gute Verteilung: SDBM, DJB2, DJB2a
- schreckliche Verbreitung: Loselose
Aktualisieren
Murmeln? Sicher warum nicht
Aktualisieren
@whatshisname fragte sich, wie sich ein CRC32 verhalten würde und fügte der Tabelle Zahlen hinzu.
CRC32 ist ziemlich gut . Nur wenige Kollisionen, aber langsamer, und der Overhead einer 1k-Nachschlagetabelle.
Schnüffeln Sie alle fehlerhaften Informationen über die CRC-Verteilung - meine schlechte
Bis heute wollte ich FNV-1a als de facto Hash-Tabellen-Hashing-Algorithmus verwenden. Aber jetzt wechsle ich zu Murmur2:
- Schneller
- Bessere Randomisierung aller Inputklassen
Und ich, wirklich wirklich hoffen , dass es etwas falsch mit dem SuperFastHash
Algorithmus , den ich gefunden ; Es ist schade, so beliebt zu sein, wie es ist.
Update: Von der MurmurHash3-Homepage bei Google :
(1) - SuperFastHash weist sehr schlechte Kollisionseigenschaften auf, die an anderer Stelle dokumentiert wurden.
Also denke ich, dass es nicht nur ich bin.
Update: Mir ist aufgefallen, warum Murmur
es schneller ist als die anderen. MurmurHash2 verarbeitet jeweils vier Bytes. Die meisten Algorithmen sind byteweise :
for each octet in Key
AddTheOctetToTheHash
Dies bedeutet, dass Murmeln mit länger werdenden Tasten seine Chance hat zu leuchten.
Aktualisieren
Ein rechtzeitiger Beitrag von Raymond Chen weist erneut darauf hin, dass "zufällige" GUIDs nicht für ihre Zufälligkeit verwendet werden sollen. Sie oder eine Teilmenge davon sind als Hash-Schlüssel ungeeignet:
Selbst der GUID-Algorithmus der Version 4 kann nicht als unvorhersehbar eingestuft werden, da der Algorithmus nicht die Qualität des Zufallszahlengenerators angibt. Der Wikipedia-Artikel für GUID enthält Primärrecherchen, die darauf hindeuten, dass zukünftige und frühere GUIDs auf der Grundlage der Kenntnis des Status des Zufallszahlengenerators vorhergesagt werden können, da der Generator nicht kryptografisch stark ist.
Zufälligkeit ist nicht dasselbe wie Kollisionsvermeidung. Aus diesem Grund wäre es ein Fehler, einen eigenen "Hashing" -Algorithmus zu erfinden, indem Sie eine Teilmenge einer "zufälligen" Guid verwenden:
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);
}
Hinweis : Auch hier setze ich "zufällige GUID" in Anführungszeichen, da es sich um die "zufällige" Variante von GUIDs handelt. Eine genauere Beschreibung wäre Type 4 UUID
. Aber niemand weiß, was Typ 4 oder Typ 1, 3 und 5 sind. Es ist also einfacher, sie als "zufällige" GUIDs zu bezeichnen.
Alle englischen Wörter spiegeln