Я протестировал несколько разных алгоритмов, измеряя скорость и количество столкновений.
Я использовал три разных набора ключей:
Для каждого корпуса было зафиксировано количество столкновений и среднее время, проведенное за хешированием.
Я проверял:
Результаты
Каждый результат содержит среднее время хеширования и количество столкновений.
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.
Все английские слова зеркал