Я протестировал несколько разных алгоритмов, измеряя скорость и количество столкновений.
Я использовал три разных набора ключей:
Для каждого корпуса было зафиксировано количество столкновений и среднее время, проведенное за хешированием.
Я проверял:
Результаты
Каждый результат содержит среднее время хеширования и количество столкновений.
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
Примечания :
Действительно ли случаются столкновения?
Да. Я начал писать свою тестовую программу, чтобы увидеть, действительно ли случаются коллизии хешей - и это не просто теоретическая конструкция. Они действительно случаются
Столкновения ФНВ-1
creamwove сталкивается с quists
Столкновения ФНВ-1а
costarring сталкивается с liquid
declinate сталкивается с macallums
altarage сталкивается с zinke
altarages сталкивается с zinkes
Murmur2 столкновения
cataract сталкивается с periti
roquette сталкивается с skivie
shawl сталкивается с stormbound
dowlases сталкивается с tramontane
cricketings сталкивается с twanger
longans сталкивается с whigs
DJB2 столкновения
hetairas сталкивается с mentioner
heliotropes сталкивается с neurospora
depravement сталкивается с serafins
stylist сталкивается с subgenera
joyful сталкивается с synaphea
redescribed сталкивается с urites
dram сталкивается с vivency
DJB2a столкновения
haggadot сталкивается с loathsomenesses
adorablenesses сталкивается с rentability
playwright сталкивается с snush
playwrighting сталкивается с snushing
treponematoses сталкивается с waterbeds
CRC32 столкновения
codding сталкивается с gnu
exhibiters сталкивается с schlager
SuperFastHash столкновения
dahabiah сталкивается с drapability
encharm сталкивается с enclave
grahams сталкивается с gramary
- ... отсечь 79 столкновений ...
night сталкивается с vigil
nights сталкивается с vigils
finks сталкивается с vinic
Randomnessification
Другая субъективная мера - насколько случайным образом распределены хэши. Отображение полученных HashTables показывает, насколько равномерно распределяются данные. Все хеш-функции показывают хорошее распределение при линейном отображении таблицы:

Или как карта Гильберта ( XKCD всегда актуален ):

Кроме случаев , когда хэширования число строк ( "1", "2", ..., "216553") (например, почтовые индексы ), где модели начинают появляться в большинстве алгоритмов хэширования:
SDBM :

DJB2a :

FNV-1 :

Все, кроме FNV-1a , которые все еще выглядят довольно случайными для меня:

Фактически, Murmur2, кажется, имеет даже лучшую случайность с Numbersчем FNV-1a:

Когда я смотрю на FNV-1aкарту «число», я думаю, что вижу тонкие вертикальные узоры. С Murmur я не вижу никаких закономерностей. Как вы думаете?
Дополнительное значение *в таблице обозначает, насколько плоха случайность. С FNV-1aявляется лучшим, и DJB2xявляется худшим:
Murmur2: .
FNV-1a: .
FNV-1: ▪
DJB2: ▪▪
DJB2a: ▪▪
SDBM: ▪▪▪
SuperFastHash: .
CRC: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
Loselose: ▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪
▪▪▪▪▪▪▪▪▪▪▪▪▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
Первоначально я написал эту программу, чтобы решить, нужно ли мне беспокоиться о столкновениях.
И тогда это превратилось в то, что хэш-функции были достаточно случайными.
Алгоритм FNV-1a
Хэш FNV1 поставляется в вариантах, которые возвращают 32, 64, 128, 256, 512 и 1024-битные хэши.
Алгоритм FNV-1a является:
hash = FNV_offset_basis
for each octetOfData to be hashed
hash = hash xor octetOfData
hash = hash * FNV_prime
return hash
Где константы FNV_offset_basisи FNV_primeзависят от размера возвращаемого хеша:
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
Смотрите главную страницу FNV для деталей.
Все мои результаты с 32-битным вариантом.
FNV-1 лучше, чем FNV-1a?
FNV-1a лучше вокруг. Было больше столкновений с FNV-1a при использовании английского слова corpus:
Hash Word Collisions
====== ===============
FNV-1 1
FNV-1a 4
Теперь сравните строчные и прописные буквы:
Hash lowercase word Collisions UPPERCASE word collisions
====== ========================= =========================
FNV-1 1 9
FNV-1a 4 11
В этом случае FNV-1a не «на 400%» хуже, чем FN-1, только на 20% хуже.
Я думаю, что более важным выводом является то, что существует два класса алгоритмов, когда речь идет о столкновениях:
- редкие столкновения : FNV-1, FNV-1a, DJB2, DJB2a, SDBM
- Общие коллизии : SuperFastHash, Loselose
И затем, насколько равномерно распределены хэши:
- выдающийся дистрибутив: Murmur2, FNV-1a, SuperFastHas
- отличное распределение: FNV-1
- хорошее распределение: SDBM, DJB2, DJB2a
- ужасное распределение: Loselose
Обновить
Ропщите? Конечно почему нет
Обновить
@whatshisname задалась вопросом, как будет работать CRC32 , добавила числа в таблицу.
CRC32 довольно хорош . Мало коллизий, но медленнее, и накладные расходы таблицы поиска 1k.
Отсеки все ошибочные материалы о распространении CRC - мой плохой
До сегодняшнего дня я собирался использовать FNV-1a в качестве своего фактического алгоритма хэширования хеш-таблицы. Но теперь я перехожу на Murmur2:
- Быстрее
- Лучшая рандомизация всех классов ввода
И я действительно, очень надеюсь, что что-то не так с SuperFastHashалгоритмом, который я нашел ; это слишком плохо, чтобы быть таким же популярным.
Обновление: с домашней страницы MurmurHash3 в Google :
(1) - SuperFastHash имеет очень плохие свойства столкновения, которые были задокументированы в другом месте.
Так что, думаю, это не только я.
Обновление: я понял, почему Murmurбыстрее, чем другие. MurmurHash2 работает с четырьмя байтами одновременно. Большинство алгоритмов побайтно :
for each octet in Key
AddTheOctetToTheHash
Это означает, что когда ключи становятся длиннее, Murmur получает шанс сиять.
Обновить
Своевременное сообщение Рэймонда Чена подтверждает тот факт, что «случайные» GUID не предназначены для их случайности. Они или их часть не подходят в качестве хеш-ключа:
Даже алгоритм GUID Версии 4 не гарантированно непредсказуем, поскольку алгоритм не определяет качество генератора случайных чисел. Статья Википедии для GUID содержит первичное исследование, которое предполагает, что будущие и предыдущие GUID могут быть предсказаны на основе знания состояния генератора случайных чисел, поскольку генератор не является криптографически стойким.
Randomess - это не то же самое, что избегать столкновений; вот почему было бы ошибкой пытаться изобрести свой собственный алгоритм «хэширования», взяв некоторое подмножество «случайного» guid:
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);
}
Примечание : опять же, в кавычки я помещаю «случайный GUID» , потому что это «случайный» вариант GUID. Более точное описание будет Type 4 UUID. Но никто не знает, что типа 4 или 1, 3 и 5. Так что проще назвать их «случайными» GUID.
Все английские слова зеркал