- PVSM.RU - https://www.pvsm.ru -

Несколько простых хеш-функций и их свойства

В процессе работы над хеш-таблицей возник обычный вопрос: какую из известных хеш-функций использовать. Образцов таких функций в сети множество, есть и «любимчики», использовавшиеся ранее и показавшие неплохой результат, в основном оценивавшийся «на глаз» — длины цепочек в хеш-таблице на боевых данных «примерно равны», значит, все работает так, как нужно; отдельные выбросы… ну что ж, ну выбросы, бывает.

В этот раз, наткнувшись на статью [1] и воодушевившись ею, решил получить более или менее объективную сравнительную оценку хеш-функций. Для этого были сформулированы требования к ним, в число которых вошли следующие:

  • функция должна возвращать 32-битное целое значение;
  • функция должна получать на входе zero-terminated string (без явно заданной длины);
  • функция должна быть достаточно быстрой (по сравнению с конкурентами);
  • функция должна давать как можно более равномерное распределение хэш-значения;
  • (несколько необычное требование, вытекающее из особенностей конкретного применения) функция должна давать как можно более равномерное распределение хэш-значения, если в качестве этого значения использовать любой байт возвращенного ею числа.

В качестве тестовых данных я воспользовался тремя словарями из вышеупомянутой статьи, автору которой весьма признателен за сэкономленное время. Словари были преобразованы в windows-1251 и слиты в один файл. Всего получилось 327049 различных слов.

Приступим

Первой жертвой экспериментов пала любимица с магической цифрой 37, ранее бездумно применявшаяся для хеширования всего и вся:

unsigned int HashH37(const char * str)
{

	unsigned int hash = 2139062143;

	for(; *str; str++)
		hash = 37 * hash + *str;

	return hash;

}

Одного взгляда на гистограмму было достаточно, чтобы понять, что выбросы, конечно, бывают, да, бывают… но не такие же
h37 [2]
Впрочем, младшие два байта результата вполне юзабельны
h37 0 [3]
h37 1 [4]
а вот старшие как раз и дают то, что доводит общую картину до категории «печальная»
h37 2 [5]
h37 3 [6]
Итак, по «любимице» вывод: там, где достаточно 16 бит результата, функция вполне пригодна, в качестве же полного 32-битного хеша не годится абсолютно.

Другие кандидаты

Естественно, после увиденного привычной хеш-функции пришлось подыскивать замену. Не мудрствуя лукаво, просмотрел все функции, подходящие по первому пункту (т.е. не требующие знания длины строки перед вычислением хеша), в статье [7], хеш-функциям и посвященной.
В число кандидатов попали (названия сохраняю оригинальные)

  • Js
  • PJW
  • ELF
  • BKDR
  • SDBM
  • DJB
  • AP
  • FAQ6
  • Rot13
  • Ly
  • Rs

Из перечисленных только функции FAQ6, Rot13, Ly и Rs показали удовлетворительные результаты. Для остальных без лишних слов приведу распределения полного 32-битного значения:

Функция Js

Js [8]

Функция PJW

PJW [9]

Функция ELF

ELF [10]

Функция BKDR

BKDR [11]

Функция SDBM

SDBM [12]

Функция DJB

DJB [13]

Функция AP

AP [14]

Победители

Для признанных «подходящими» функций приведу полный код (немного измененный по сравнению с данным в статье-источнике, изменения в основном связаны с требованием отстутствия явно заданной длины хешируемой строки) и распределения как полного 32-битного значения, так и каждого байта.

Функция FAQ6

unsigned int HashFAQ6(const char * str)
{

	unsigned int hash = 0;

	for (; *str; str++)
	{
		hash += (unsigned char)(*str);
		hash += (hash << 10);
		hash ^= (hash >> 6);
	}
	hash += (hash << 3);
	hash ^= (hash >> 11);
	hash += (hash << 15);

	return hash;

}

32-битное значение
FAQ6 [15]
первый байт
FAQ6 0 [16]
второй байт
FAQ6 1 [17]
третий байт
FAQ6 2 [18]
четвертый байт
FAQ6 3 [19]

Функция Rot13

unsigned int HashRot13(const char * str)
{

	unsigned int hash = 0;

	for(; *str; str++)
	{
		hash += (unsigned char)(*str);
		hash -= (hash << 13) | (hash >> 19);
	}

	return hash;

}

32-битное значение
Rot13 [20]
первый байт
Rot13 0 [21]
второй байт
Rot13 1 [22]
третий байт
Rot13 2 [23]
четвертый байт
Rot13 3 [24]

Функция Ly

unsigned int HashLy(const char * str)
{

	unsigned int hash = 0;

	for(; *str; str++)
		hash = (hash * 1664525) + (unsigned char)(*str) + 1013904223;

	return hash;

}

32-битное значение
Ly [25]
первый байт
Ly 0 [26]
второй байт
Ly 1 [27]
третий байт
Ly 2 [28]
четвертый байт
Ly 3 [29]

Функция Rs

unsigned int HashRs(const char * str)
{

	static const unsigned int b = 378551;
	unsigned int a = 63689;
	unsigned int hash = 0;

	for(; *str; str++)
	{
		hash = hash * a + (unsigned char)(*str);
		a *= b;
	}

	return hash;

}

32-битное значение
Rs [30]
первый байт
Rs 0 [31]
второй байт
Rs 1 [32]
третий байт
Rs 2 [33]
четвертый байт
Rs 3 [34]

Производительность

Из всех протестированных функций наибольшую скорость работы продемонстрировала DJB. Несмотря на то, что в число «годных» функций она не попала, затраченное ею время на обработку всех тестовых слов я принял за 100% и соотнес с ним время работы остальных функций. Получилось следующее:

DJB 100
SDBM 111
BKDR 113
H37 113
Ly 122
Js 125
Rs 125
Rot13 145
AP 159
ELF 184
PJW 191
FAQ6 205

Если оставить в таблице только выбранные для использования функции, получим

Ly 122
Rs 125
Rot13 145
FAQ6 205

Выводы

Рассмотрев статистические характеристики некоторых известных хеш-функций, мы выбрали из них те, что показали наиболее равномерное распределение как по полному 32-битному значению, так и по отдельным байтам и захабрили (для истории?) их исходный код. Следует отметить, что не вошедшие в четверку «лидеров» функции могут оказаться предпочтительными при других условиях, например, если полученное значение берется по какому-либо модулю. Мы такие случаи не рассматривали.

Благодарю за внимание.

Автор: madcomaker

Источник [35]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/programmirovanie/58836

Ссылки в тексте:

[1] статью: http://vak.ru/doku.php/proj/hash/efficiency

[2] Image: http://postimg.org/image/8ye4jc8sj/

[3] Image: http://postimg.org/image/8stdcsrvz/

[4] Image: http://postimg.org/image/5d9wci4h1/

[5] Image: http://postimg.org/image/c66uux6np/

[6] Image: http://postimg.org/image/6prlee1wl/

[7] статье: http://vak.ru/doku.php/proj/hash/sources

[8] Image: http://postimg.org/image/68ethi5ut/

[9] Image: http://postimg.org/image/736yje4k9/

[10] Image: http://postimg.org/image/8m596kh9f/

[11] Image: http://postimg.org/image/ym8g0ecap/

[12] Image: http://postimg.org/image/geom0dr05/

[13] Image: http://postimg.org/image/q1tyfhd8n/

[14] Image: http://postimg.org/image/4y0vky8e1/

[15] Image: http://postimg.org/image/s95jpb1g7/

[16] Image: http://postimg.org/image/n9kv95xp1/

[17] Image: http://postimg.org/image/v7jvok0d1/

[18] Image: http://postimg.org/image/qnwh7alwx/

[19] Image: http://postimg.org/image/g0ym6wbkp/

[20] Image: http://postimg.org/image/5314txr4p/

[21] Image: http://postimg.org/image/8lpv5muyz/

[22] Image: http://postimg.org/image/avhxolixj/

[23] Image: http://postimg.org/image/4xss60eun/

[24] Image: http://postimg.org/image/g24hba4m5/

[25] Image: http://postimg.org/image/snn9s0eh9/

[26] Image: http://postimg.org/image/c544cxgel/

[27] Image: http://postimg.org/image/3kuvnn02t/

[28] Image: http://postimg.org/image/8ze165d8b/

[29] Image: http://postimg.org/image/7pt8rr6yd/

[30] Image: http://postimg.org/image/71gjbvsn5/

[31] Image: http://postimg.org/image/kxzajo7l7/

[32] Image: http://postimg.org/image/s43ml8krd/

[33] Image: http://postimg.org/image/tn4ufxggp/

[34] Image: http://postimg.org/image/zbwqt903l/

[35] Источник: http://habrahabr.ru/post/219139/