Testowałem różne algorytmy, mierząc prędkość i liczbę kolizji.
Użyłem trzech różnych zestawów kluczy:
Dla każdego korpusu rejestrowano liczbę kolizji i średni czas haszowania.
Testowałem:
Wyniki
Każdy wynik zawiera średni czas mieszania i liczbę kolizji
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
Uwagi :
Czy faktycznie dochodzi do kolizji?
Tak. Zacząłem pisać mój program testowy, aby sprawdzić, czy rzeczywiście występują kolizje skrótów - i nie są to tylko teoretyczne konstrukcje. Rzeczywiście się zdarzają:
Zderzenia FNV-1
creamwove
koliduje z quists
Zderzenia FNV-1a
costarring
koliduje z liquid
declinate
koliduje z macallums
altarage
koliduje z zinke
altarages
koliduje z zinkes
Kolizje Murmur2
cataract
koliduje z periti
roquette
koliduje z skivie
shawl
koliduje z stormbound
dowlases
koliduje z tramontane
cricketings
koliduje z twanger
longans
koliduje z whigs
Zderzenia DJB2
hetairas
koliduje z mentioner
heliotropes
koliduje z neurospora
depravement
koliduje z serafins
stylist
koliduje z subgenera
joyful
koliduje z synaphea
redescribed
koliduje z urites
dram
koliduje z vivency
Zderzenia DJB2a
haggadot
koliduje z loathsomenesses
adorablenesses
koliduje z rentability
playwright
koliduje z snush
playwrighting
koliduje z snushing
treponematoses
koliduje z waterbeds
Zderzenia CRC32
codding
koliduje z gnu
exhibiters
koliduje z schlager
Kolizje SuperFastHash
dahabiah
koliduje z drapability
encharm
koliduje z enclave
grahams
koliduje z gramary
- ... snip 79 kolizji ...
night
koliduje z vigil
nights
koliduje z vigils
finks
koliduje z vinic
Randomnessification
Inną subiektywną miarą jest losowe rozmieszczenie skrótów. Odwzorowanie powstałych tabel skrótów pokazuje, jak równomiernie dane są rozmieszczone. Wszystkie funkcje skrótu wykazują dobry rozkład podczas liniowego mapowania tabeli:
Lub jako mapa Hilberta ( XKCD jest zawsze odpowiedni ):
Z wyjątkiem gdy mieszania ciągów numer ( "1"
, "2"
, ..., "216553"
) (na przykład kody pocztowe ), gdzie wzorce zaczynają się pojawiać w większości algorytmów mieszaja:
SDBM :
DJB2a :
FNV-1 :
Wszystkie oprócz FNV-1a , które nadal wyglądają dla mnie dość losowo:
W rzeczywistości Murmur2 wydaje się mieć jeszcze lepszą losowość Numbers
niż FNV-1a
:
Kiedy patrzę na FNV-1a
mapę „liczbową”, myślę, że widzę subtelne pionowe wzory. Dzięki Murmurowi nie widzę żadnych wzorów. Co myślisz?
Dodatek *
w tabeli wskazuje, jak zła jest losowość. Z FNV-1a
bycia najlepszym, a DJB2x
będąc najgorsze:
Murmur2: .
FNV-1a: .
FNV-1: ▪
DJB2: ▪▪
DJB2a: ▪▪
SDBM: ▪▪▪
SuperFastHash: .
CRC: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
Loselose: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪
▪▪▪▪▪▪▪▪▪▪▪▪▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
Pierwotnie napisałem ten program, aby zdecydować, czy w ogóle muszę się martwić o kolizje: tak.
A potem okazało się, że funkcje haszujące były wystarczająco losowe.
Algorytm FNV-1a
Skrót FNV1 występuje w wariantach, które zwracają skróty 32, 64, 128, 256, 512 i 1024 bitów.
Algorytm FNV-1a jest:
hash = FNV_offset_basis
for each octetOfData to be hashed
hash = hash xor octetOfData
hash = hash * FNV_prime
return hash
Gdzie stałe FNV_offset_basis
i FNV_prime
zależą od żądanego rozmiaru zwracanej wartości skrótu:
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
Szczegółowe informacje można znaleźć na głównej stronie FNV .
Wszystkie moje wyniki dotyczą wariantu 32-bitowego.
FNV-1 lepszy niż FNV-1a?
Nie. FNV-1a jest lepszy. Podczas używania angielskiego słowa corpus doszło do większej liczby kolizji z FNV-1a:
Hash Word Collisions
====== ===============
FNV-1 1
FNV-1a 4
Teraz porównaj małe i wielkie litery:
Hash lowercase word Collisions UPPERCASE word collisions
====== ========================= =========================
FNV-1 1 9
FNV-1a 4 11
W tym przypadku FNV-1a nie jest „400%” gorszy niż FN-1, tylko 20% gorzej.
Myślę, że ważniejsze jest to, że istnieją dwie klasy algorytmów, jeśli chodzi o kolizje:
- rzadkie kolizje : FNV-1, FNV-1a, DJB2, DJB2a, SDBM
- typowe kolizje : SuperFastHash, Loselose
A potem jest to, jak równomiernie rozłożone są skróty:
- znakomita dystrybucja: Murmur2, FNV-1a, SuperFastHas
- doskonała dystrybucja: FNV-1
- dobra dystrybucja: SDBM, DJB2, DJB2a
- okropna dystrybucja: Loselose
Aktualizacja
Szmer? Jasne, czemu nie
Aktualizacja
@ whatshisname zastanawiał się, jak będzie działać CRC32 , dodał liczby do tabeli.
CRC32 jest całkiem niezły . Mało kolizji, ale wolniej, i narzut 1-krotnej tabeli odnośników.
Zniszcz wszystkie błędne informacje o dystrybucji CRC - moje złe
Do dzisiaj miałem używać FNV-1a jako mojego de facto algorytmu haszującego tablicę skrótów. Ale teraz przełączam się na Murmur2:
- Szybciej
- Lepsza losowość wszystkich klas danych wejściowych
I naprawdę, naprawdę mam nadzieję, że coś jest nie tak z SuperFastHash
algorytmem, który znalazłem ; szkoda być popularnym.
Aktualizacja: Od głównej MurmurHash3 w Google :
(1) - SuperFastHash ma bardzo słabe właściwości kolizji, które zostały udokumentowane gdzie indziej.
Myślę, że to nie tylko ja.
Aktualizacja: Zrozumiałem, dlaczego Murmur
jest szybszy od innych. MurmurHash2 działa na czterech bajtach jednocześnie. Większość algorytmów jest bajt po bajcie :
for each octet in Key
AddTheOctetToTheHash
Oznacza to, że gdy klucze stają się dłuższe, Murmur ma szansę zabłysnąć.
Aktualizacja
Terminowy post Raymonda Chena potwierdza fakt, że „losowe” identyfikatory GUID nie są przeznaczone do ich losowości. One lub ich część nie są odpowiednie jako klucz skrótu:
Nawet algorytm GUID w wersji 4 nie jest gwarantowany jako nieprzewidywalny, ponieważ algorytm nie określa jakości generatora liczb losowych. Artykuł w Wikipedii dotyczący GUID zawiera podstawowe badania, które sugerują, że przyszłe i poprzednie GUID można przewidzieć na podstawie wiedzy o stanie generatora liczb losowych, ponieważ generator nie jest silny kryptograficznie.
Losowość to nie to samo, co unikanie kolizji; dlatego błędem byłoby wymyślić własny algorytm „mieszający”, przyjmując pewien podzbiór „losowego” przewodnika:
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);
}
Uwaga : Znów wstawiłem „przypadkowy GUID” w cudzysłowie, ponieważ jest to „losowy” wariant GUID. Bardziej dokładny opis byłby Type 4 UUID
. Ale nikt nie wie, jaki jest typ 4 lub typy 1, 3 i 5. Łatwiej więc nazwać je „losowymi” identyfikatorami GUID.
Lustra wszystkich angielskich słów