in English

Алгоритмы общего назначения

Простые хэш-функции высокой производительности

Уже знакомы с первым выпуском? Таки второй вышел!


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

Что отличает проведенный мною тест? Здесь с целью получения достоверных и стабилных результатов выполняется несколько паетных тесирований на совершенно разных наборах данных. При этом каждое тестирование включает несколько подтестов, каждый из котторых в определенной степени отличается по сложности тестового материала.

Зачем все это? Да, как правило, распространены либо сугубо статистические тесты, методика которых схожа с методикой тестирования генераторов псевдослучайных чисел и поточных шифров, либо тесты на одном наборе информации. Но говорить о хэш-функции как о хорошей сложно, исходя из одного теста. Более того, такую функцию можно просто оптимизировать под определенный набор данных, строки определенной длины или в определенном промежутке символов. Один тест может не выявить слабые места алгоритма. Здесь же вы найдете комплексное и мощное тестирование на богатом наборе тестового материала. Все это, возможно, приведет к самым неожиданным результатам.

Приступим к тестированию.

Тестовый набор содержит слова, отсортированные в алфавитном порядке, дубликаты удалены. В качестве основы были взяты заголовки из Русской Википедии, Большой советской энциклопедии, Большого англо-русского словаря и других словарных источников в общем колличестве 2726299 уникальных слова. Важно отменить, что все тексты - в кодировке cp866, минимальная длина - 3 символа, регистр - верхний. Тестовый набор можно скачать здесь или здесь. Инструментарий тестирования, который включает исходные тексты всех алгоритмов, можно найти здесь. Результаты тестов в виде электронной таблицы можно найти здесь.

Порядок тестово таков: хэшируем все слова в текстовом файле, сортируем результаты, находим коллизии. Чем меньше дубликатов, тем лучше алгоритм хэширования, поскольку идентичных слов в тестовом наборе у нас нет.

Описание вариантов теста:

  • Тест A: простое хэширование
  • Тест B: добавляем символ конца строки к слову
  • Тест C: добавляем четыре символа конца строки к слову
  • Тест D: добавляем к слову пять символов ABCDE
  • Тест E: добавляем к слову его дубликат
  • Тест F: добавляем к слову его три дубликата
  • Тест G: изменяем порядок символов в слове на обратный
  • Тест H: делаем из слова его палиндром
Тест A - простой, здесь хэшируются только слова. Проблемы могут быть только у функций, которые “сыпятся” на коротких словах.
Тест B - небольшая разминка перед сложной проверкой. Добавляем гарантированное совпадение в конец и ждем коллизий.
Тест C - весьма экстримальный, поскольку здесь мы встречаем последовательное повторение одного символа, которая может ударить по лавинному эффекту.
Тест D - тоже не прост, у каждого слова теперь гарантировано 5 совпадений. Только качественная функция здесь не даст значительно больше коллизий.
Тест E - самый сложный для прохождения. Как правило, здесь почти все показывают результаты немного хуже или даже дают всплеск столкновений.
Тест F - аналогично предыдущему тесту очень сложен, однако результаты могут быть несколько лучше ввиду большего количества итераций
Тест G - это проверка корректности первого теста на стабильность результатов. В этом тесте участвуют неосмыссленные слова, хотя набор символов идентичен
Тест H - это проверка логики алгоритма. Использование одного текста в прямом и обратном порядке не должно снизить лавинный эффект и вызвать увеличение числа столкновений.

Функции семейства MaHash были разработаны специально для данного теста, остальные участники вступили в схватку без подготовки. В процессе тестирования были разработаны модификации некоторых популярных алгоритмов, которые показали результаты лучших оригинальных.

MaHash7

Что отличает эту функцию? Правильно, бросается в глаза таблица замены (подстановки). Зачем она в таком простом алгоритме? Прежде всего затем, что таблица замены - это сложная функция, которую просто описали несколько иным способом. При этом такую функцию очень легко реализовать как программно, так и аппаратно, скорость запроса к таблице высока в обоих случаях. У функции есть параметр - число итераций. Статистически, лучшим параметром будет одна итерация для большинства текстов. Хотя для оригинальной функции оптимальными будут три итерации. Сама таблица подстановки идентична таковой в криптоалгоритме Skipjack. Результаты не лучшие, но конструкция имеет право на жизнь.

static const unsigned char sTable[256] =
{
  0xa3,0xd7,0x09,0x83,0xf8,0x48,0xf6,0xf4,0xb3,0x21,0x15,0x78,0x99,0xb1,0xaf,0xf9,
  0xe7,0x2d,0x4d,0x8a,0xce,0x4c,0xca,0x2e,0x52,0x95,0xd9,0x1e,0x4e,0x38,0x44,0x28,
  0x0a,0xdf,0x02,0xa0,0x17,0xf1,0x60,0x68,0x12,0xb7,0x7a,0xc3,0xe9,0xfa,0x3d,0x53,
  0x96,0x84,0x6b,0xba,0xf2,0x63,0x9a,0x19,0x7c,0xae,0xe5,0xf5,0xf7,0x16,0x6a,0xa2,
  0x39,0xb6,0x7b,0x0f,0xc1,0x93,0x81,0x1b,0xee,0xb4,0x1a,0xea,0xd0,0x91,0x2f,0xb8,
  0x55,0xb9,0xda,0x85,0x3f,0x41,0xbf,0xe0,0x5a,0x58,0x80,0x5f,0x66,0x0b,0xd8,0x90,
  0x35,0xd5,0xc0,0xa7,0x33,0x06,0x65,0x69,0x45,0x00,0x94,0x56,0x6d,0x98,0x9b,0x76,
  0x97,0xfc,0xb2,0xc2,0xb0,0xfe,0xdb,0x20,0xe1,0xeb,0xd6,0xe4,0xdd,0x47,0x4a,0x1d,
  0x42,0xed,0x9e,0x6e,0x49,0x3c,0xcd,0x43,0x27,0xd2,0x07,0xd4,0xde,0xc7,0x67,0x18,
  0x89,0xcb,0x30,0x1f,0x8d,0xc6,0x8f,0xaa,0xc8,0x74,0xdc,0xc9,0x5d,0x5c,0x31,0xa4,
  0x70,0x88,0x61,0x2c,0x9f,0x0d,0x2b,0x87,0x50,0x82,0x54,0x64,0x26,0x7d,0x03,0x40,
  0x34,0x4b,0x1c,0x73,0xd1,0xc4,0xfd,0x3b,0xcc,0xfb,0x7f,0xab,0xe6,0x3e,0x5b,0xa5,
  0xad,0x04,0x23,0x9c,0x14,0x51,0x22,0xf0,0x29,0x79,0x71,0x7e,0xff,0x8c,0x0e,0xe2,
  0x0c,0xef,0xbc,0x72,0x75,0x6f,0x37,0xa1,0xec,0xd3,0x8e,0x62,0x8b,0x86,0x10,0xe8,
  0x08,0x77,0x11,0xbe,0x92,0x4f,0x24,0xc5,0x32,0x36,0x9d,0xcf,0xf3,0xa6,0xbb,0xac,
  0x5e,0x6c,0xa9,0x13,0x57,0x25,0xb5,0xe3,0xbd,0xa8,0x3a,0x01,0x05,0x59,0x2a,0x46
};



#define LROT14(x) (((x) << 14) | ((x) >> 18 )) #define ITERATIONS 1 unsigned int MaHash7 (unsigned char *str, unsigned int len) { unsigned int hash = len, i; for (i = 0; i != len * ITERATIONS; i++) { str += (i % len); hash = LROT13 (hash + ((hash << 8) ^ (hash >> 12))) - sTable[(*str + i) & 255]; } return hash; }

MaHash9

Что изменено? Другие константы сдвигов и циклический сдвиг влево на 14 бит, дополнительная операция побитового НЕ. Как результат - немного лучше показатели в ряде тестов, но в итоге изменения себя не оправдали. В итоге - далеко не первое место, так же - твердый середнячок.


#define LROT14(x) (((x) << 14) | ((x) >> 18 ))

unsigned int MaHash9 (unsigned char *str, unsigned int len)
{
    unsigned int hash = len, i;

    for (i = 0; i != len; i++, str++)
    {

        hash = LROT14( ~hash + ((hash << 6) ^ (hash >> 11))) - sTable[(*str + i) & 255];

    }

    return hash;
}

MaHash2

Что же здесь нового? По-сути, это два алгоритма MaHash для параллельного подсчета двух 32-разрядных значений. На каждом этапе результат вычисления оригинальной и видоизмененной функции с обратным циклическим сдвигом вправо взаимозаменяются. Результатом хэш-функции будет сумма подфункций по модулю 2 (операция XOR). Такая конструкция позволила улучшить результаты алгоритма в плане числа коллизий. Достоинства алгоритма - отсутствие операций деления, умножения (наиболее медленных на современных неспециализированных процессорах) и возможность распараллеливания. С неба звезд не хватает, но показывает сравнительно хорошие результаты - восьмое место в рейтинге с небольшим отставанием от ближайших соперников.



#define LROT(x) (((x) << 11) | ((x) >> 21 ))
#define RROT(x) (((x) << 21) | ((x) >> 11 ))


unsigned int MaHash2 (unsigned char *str, unsigned int len)
{
    unsigned int t, hash1 = len, hash2 = len, i;
    for (i = 0; i != len; i++, str++)
    {

        hash1 += sTable[(*str + i) & 255];
        hash1 = LROT(hash1 + ((hash1 << 6) ^ (hash1 >> 8)));
        hash2 += sTable[(*str + i) & 255];
        hash2 = RROT(hash2 + ((hash2 << 6) ^ (hash2 >> 8)));

        t = hash1;
        hash1 = hash2;
        hash2 = t;

    }



    return hash1 ^ hash2;
}

MaHash8

Найдем отличия? Изменились сдвиги, циклический сдвиг увеличился не несколько разрядов. На смену взаимозамены результатов подфункций теперь они “собираются” из младшего родного слова и старшего слова альтернативной функции. Показал в среднем лучшие среди участников результаты.


#define LROT14(x) (((x) << 14) | ((x) >> 18 ))
#define RROT14(x) (((x) << 18) | ((x) >> 14 ))

unsigned int MaHash8 (unsigned char *str, unsigned int len) { unsigned int sh1, sh2, hash1 = len, hash2 = len, i; for (i = 0; i != len; i++, str++) { hash1 += sTable[(*str + i) & 255]; hash1 = LROT14(hash1 + ((hash1 << 6) ^ (hash1 >> 11))); hash2 += sTable[(*str + i) & 255]; hash2 = RROT14(hash2 + ((hash2 << 6) ^ (hash2 >> 11))); sh1 = hash1; sh2 = hash2; hash1 = ((sh1 >> 16) & 0xffff) | (sh2 & 0xffff) << 16; hash2 = ((sh2 >> 16) & 0xffff) | (sh1 & 0xffff) << 16; } return hash1 ^ hash2; }

MaHash4

Вернулись обратно? Да, здесь опять классические сдвиги и обычный циклический сдвиг. Только вместо взаимозамены опять “сборка” значений. Эта функция, как и прошлая, стабильно показывает очень хорошие результы, не имеет “провалов”. Не даром заняла второе место в общем зачете и победила в сокращенном тесте.


#define LROT(x) (((x) << 11) | ((x) >> 21 ))
unsigned int MaHash4 (unsigned char *str, unsigned int len) { unsigned int sh1, sh2, hash1 = len, hash2 = len, i; for (i = 0; i != len; i++, str++) { hash1 += sTable[(*str + i) & 255]; hash1 = LROT (hash1 + ((hash1 << 6) ^ (hash1 >> 8))); hash2 += sTable[(*str + i) & 255]; hash2 = RROT (hash2 + ((hash2 << 6) ^ (hash2 >> 8))); sh1 = hash1; sh2 = hash2;
hash1 = ((sh1 >> 16) & 0xffff) | (sh2 & 0xffff) << 16; hash2 = ((sh2 >> 16) & 0xffff) | (sh1 & 0xffff) << 16; } return hash1 ^ hash2; }

MaHash5

Усложнили? Да, немного. Добавлено дополнительное преобразование в конце шага. Результаты стабильны для обоих тестов, хотя в сравнении с прошлой функцией усложнение, которое немного улучшило результат первого теста, не оправдано. Как результат, пятое место в зачете и фактическая несостоятельность.


#define LROT(x) (((x) << 11) | ((x) >> 21 ))
unsigned int MaHash5 (unsigned char *str, unsigned int len) { unsigned int sh1, sh2, hash1 = len, hash2 = len, i; for (i = 0; i != len; i++, str++) { hash1 += sTable[(*str + i) & 255]; hash1 = LROT (hash1 + ((hash1 << 6) ^ (hash1 >> 8))); hash2 += sTable[(*str + i) & 255]; hash2 = RROT (hash2 + ((hash2 << 6) ^ (hash2 >> 8))); sh1 = LROT (hash1 + ((hash1 << 6) ^ (hash1 >> 8))); sh2 = RROT (hash2 + ((hash2 << 6) ^ (hash2 >> 8))); hash1 = ((sh1 >> 16) & 0xffff) | (sh2 & 0xffff) << 16; hash2 = ((sh2 >> 16) & 0xffff) | (sh1 & 0xffff) << 16; } return hash1 ^ hash2; }

MaHash11

Знакомо? Да, ведь это MaHash2 с другими константами сдвигов. Причем, теперь для каждой подфункции теперь применяется свое значение циклического сдвига. Показала себя очень хорошо, без резких счачков коллизий в сложных наборах. Третье место в нашем рейтинге.


#define LROT12(x) (((x) << 12) | ((x) >> 20 ))
#define RROT13(x) (((x) << 19) | ((x) >> 13 ))


unsigned int MaHash11 (unsigned char *str, unsigned int len)
{
    unsigned int t, hash1 = len, hash2 = len, i;
    for (i = 0; i != len; i++, str++)
    {

        hash1 += sTable[(*str + i) & 255];
        hash1 = LROT12(hash1 + ((hash1 << 6) ^ (hash1 >> 14)));
        hash2 += sTable[(*str + i) & 255];
        hash2 = RROT13(hash2 + ((hash2 << 6) ^ (hash2 >> 14)));

        t = hash1;
        hash1 = hash2;
        hash2 = t;

    }



    return hash1 ^ hash2;
}



MaHash10

Что-то новенькое ? Не совсем, но стоит особняком, выделяется немного новой структурой. Здесь уже нет двух параллельных подфункций, хотя и вычисляются два 32-разядных значения на каждом шаге. Как и впрошлой функции, циклические сдвиги различны для каждого вычисления. Как оказалась, такая конструкция имеет небольшие всплески коллизий и от классической пока отсказываться спешить не следует. Из всего семейства показала худший результат, хотя и опередила некоторых именитых соперников.



#define LROT14(x) (((x) << 14) | ((x) >> 18 ))
#define RROT14(x) (((x) << 18) | ((x) >> 14 ))


unsigned int MaHash10 (unsigned char *str, unsigned int len)
{
    unsigned int hash1 = len, hash2 = len, i, value;
    for (i = 0; i != len; i++, str++)
    {


        value = sTable[(*str + i) & 255];
        hash1 =  LROT14(hash2 + ((hash2 << 6) ^ (hash2 >> 11)));
        hash1 += value;
        hash2 =  RROT15(hash1 + ((hash1 << 6) ^ (hash1 >> 11)));
        hash2 += value;


    }



    return hash1 ^ hash2;
}




MaHash3

Опять усложняем ? И не зря. Как показали тесты, функция - одна из самых удачных в семействе. Дополнительная интерация немного снизила число коллизий на самых сложных тестах.


#define LROT(x) (((x) << 11) | ((x) >> 21 ))
#define RROT(x) (((x) << 21) | ((x) >> 11 ))

unsigned int
MaHash3 (unsigned char *str, unsigned int len)
{
  unsigned int t, hash1 = len, hash2 = len, i;
  unsigned char index;

  for (i = 0; i != len; i++, str++)
    {

      index = (*str + i) & 255;

      hash1 += sTable[index];
      hash1 = LROT (hash1 + ((hash1 << 6) ^ (hash1 >> 8)));
      hash2 += sTable[(sTable[index] + 1) & 255];
      hash2 = RROT (hash2 + ((hash2 << 6) ^ (hash2 >> 8)));
      t = hash1;
      hash1 = hash2;
      hash2 = t;

    }

  hash1 = LROT (hash1 + ((hash1 << 6) ^ (hash1 >> 8)));
  hash1 += sTable[len];
  hash2 = RROT (hash2 + ((hash2 << 6) ^ (hash2 >> 8)));
  hash2 += sTable[len];


  return hash1 ^ hash2;
}


MaBKDR

Говорят, существующие алгоритмы не улучшишь? Пусть говорят. У BKDR есть небольшой недостаток, он хорош на строках одной длины - на больших объемах текстов различной длины число коллизий увеличивается. Это легко исправить, просто добавляем счетчик символа на каждом шаге. Такая модификация, как показали исследования, значительно улучшит статистические показатели, снизив число столкновений. В то же время никаких деградаций функции по результатам тестов мной замечено не было. Значит, модификация имеет право на жизнь и такой вариант предпочтительнее оригинальной функции. И, что важно, добавлена лишь одна операция, которая выполняется быстро на современных процессорах.


unsigned int
maBKDRHash (unsigned char *str, unsigned int len)
{
  unsigned int seed = 131313;   /* 31 131 1313 13131 131313 etc.. */
  unsigned int hash = 0;
  unsigned int i = 0;

  for (i = 0; i < len; str++, i++)
    {
      hash = (hash * seed) + *str + /* maBKDR modification here*/ i;
    }
  return hash;
}


MaPrime

Простые числа? Они самые. Существующие простые хэш-функции на простых числах немного деградируют в сложных случаях, судя по моим тестам. Такие случаи - это наборы текстов, имеющие совпадающие участки. Более точно - это потому, что алгоритмы не предусмотрели четкой взаимосвязи между символом и его позицией в строке. Наш алгоритм очень прост, основан на операции умножения на простое число. Используется таблица подстановки, так же что и в других моих функциях. Как вариант, может использоваться упрощенный вариант без таблицы. Преимущества - элементарная и понятная структура, простота реализации и хорошие результаты во всех тестах.


#define PRIME_MULT 0x1FAF
#define START_PRIME 0x3A8F05C5
#define USE_SBOX
unsigned int maPrimeHash (unsigned char *buf, unsigned int len) { unsigned int hval = START_PRIME, i; for (i = 0; i != len; i++, buf++) { #ifdef USE_SBOX hval ^= sTable[(*buf + i) & 255]; #else hval += ((unsigned int)*buf) ^ i; #endif hval *= PRIME_MULT; } return hval; }

MaPrime2c

Брат-близнец? Конечно. И найти отоличия сложно. Этот алгоритм отличается константой, на которую умножается внутреннее состояние, и тем, что его стартовым значением является не фиксированное число, а длина сообщения. Результаты тоже не плохие. По соотношению качества хэширования и производительности это лучшая из моих функций.


#define PRIME_MULT 1717


unsigned int
maPrime2cHash (unsigned char *str, unsigned int len)
{
  unsigned int hash = len, i;


  for (i = 0; i != len; i++, str++)
    {

      hash ^= sTable[( *str + i) & 255];
      hash = hash * PRIME_MULT;
    }

  return hash;
}



MaPrime2d

Какой смысл опять усложнять? Он, определенно есть. Как показали автоматизированные тесты, функция показывает более стабильные результаты, нежели оригинальная. Да и дополнения не так сильно отражаются на производительности. Второй момент - это возможность параметризации, т.е. тонкой натройки под определенный набор данных. Существенный параметр здесь - это константа циклического сдвига. В тесте использован сдвиг на два бита вправо, что обеспечило усредненные результаты по тестам вцелом, однако использование некоторых других значений может быть более приемлимым в зависимости от набора текстов. К примеру - один или три бита.





unsigned int
maPrime2dHash (unsigned char *str, unsigned int len)
{
  unsigned int hash = 0, i;
  unsigned int rotate = 2;
  unsigned int seed = 0x1A4E41U;


  for (i = 0; i != len; i++, str++)
    {
      
      hash += sTable[(*str + i) & 255];
      hash = (hash << (32 - rotate) ) | (hash >> rotate);
      hash = (hash + i ) * seed;

    }


  return (hash + len) * seed;
}



MaHash8v64

Где-то уже видели эту функцию? Конечно, видели. Это - двойник алгоритма MaHash8, только теперь функция возвращает 64-разрядный хэш, тоесть оба 32-разрядных значения, не сжимая из в одно. Сам алгоритм полностью 32-разрядный, скорость его не уменьшилась. Такую манимуляцию можно провести со всеми алгоритмами семейства, в которых структура - это две подфункции. Достоинства алгоритма - высокая скорость, равная алгоритму с 32-разрядным хэшем, простота реализации и очень хорошие показатели по числу коллизий. Функция, безусловно, лидер в нашем забеге, хотя участвует вне конкурса - ведь хэш в два раза больше, чем у других участников.

Для увеличения лавинного эффекта можно использовать простую модификацию с дополнительным преобразованием финального 64-битного значения.


#define LROT14(x) (((x) << 14) | ((x) >> 18 ))
#define RROT14(x) (((x) << 18) | ((x) >> 14 ))

#ifdef HIAVAL
#define LROT64(x) (((x) << 29) | ((x) >> 34 ))
#endif

unsigned long long
MaHash8v64 (unsigned char *str, unsigned int len)
{
  unsigned int sh1, sh2, hash1 = len, hash2 = len, i;
  unsigned long long digest;

  for (i = 0; i != len; i++, str++)
    {

      hash1 += sTable[( *str + i) & 255];
      hash1 = LROT14 (hash1 + ((hash1 << 6) ^ (hash1 >> 11)));
      hash2 += sTable[( *str + i) & 255];
      hash2 = RROT14 (hash2 + ((hash2 << 6) ^ (hash2 >> 11)));

      sh1 = hash1;
      sh2 = hash2;

      hash1 = ((sh1 >> 16) & 0xffff) | ((sh2 & 0xffff) << 16);
      hash2 = ((sh2 >> 16) & 0xffff) | ((sh1 & 0xffff) << 16);

    }


#ifdef HIAVAL
  digest = (((unsigned long long) hash1) << 32)  | ((unsigned long long) hash2 );
  digest ^= LROT64(digest + ((digest << 13) ^ (digest >> 23)));
#else
  digest = (((unsigned long long) hash2) << 32)  | ((unsigned long long) hash1 );
#endif

  return digest;
}


MaHash4v64

Опять дежавю? Опять нет. Это модификация существующего алгоритма, аналогично вышерассмотренной 64-разрядной функции. Также, как и в прошлом варинте, можно усилить лавинный эффект путем добавления дополнительного преобразования. Это немного замедлит алгоритм, поскольку операции выполняются над 64-разрядными беззнаковыми числами, но функция будет выглядеть интереснее.


#define LROT(x) (((x) << 11) | ((x) >> 21 ))
#define RROT(x) (((x) << 21) | ((x) >> 11 ))

#ifdef HIAVAL
#define LROT64(x) (((x) << 29) | ((x) >> 34 ))
#endif

unsigned long long
MaHash4v64 (unsigned char *str, unsigned int len)
{
  unsigned int sh1, sh2, hash1 = len, hash2 = len, i;
  unsigned long long digest;

  for (i = 0; i != len; i++, str++)
    {

      hash1 += sTable[( *str + i) & 255];
      hash1 = LROT (hash1 + ((hash1 << 6) ^ (hash1 >> 8)));
      hash2 += sTable[( *str + i) & 255];
      hash2 = RROT (hash2 + ((hash2 << 6) ^ (hash2 >> 8)));

      sh1 = hash1;
      sh2 = hash2;

      hash1 = ((sh1 >> 16) & 0xffff) | ((sh2 & 0xffff) << 16);
      hash2 = ((sh2 >> 16) & 0xffff) | ((sh1 & 0xffff) << 16);

    }

#ifdef HIAVAL
  digest = (((unsigned long long) hash1) << 32)  | ((unsigned long long) hash2 );
  digest ^= LROT64(digest + ((digest << 13) ^ (digest >> 23)));
#else
  digest = (((unsigned long long) hash2) << 32)  | ((unsigned long long) hash1 );
#endif

  return digest;

}


MaHashMR1

Белая ворона? Более чем. Здесь мы имеет саму необычную структуру: четыре “вращающихся” 8-битных регистра накопления, которые модифицируются с помощью двойной табличной замены. На каждом шаге для накопления входного текста выбирается следующий регистр, затем все биты всех регистров циклически сдвигаются на один бит. Функция может иметь любое кол-во итераций. По всей видимости, оптимальным их числом будет три.



#define LROT1(x) (((x) << 1) | ((x) >> 31 ))
#define MR_ITERATIONS 3

union type_regs
  {
    unsigned long d32;
    unsigned char d8[4];
  };


unsigned long
MaHashMR1 (unsigned char *str, unsigned int len)
{
  unsigned int  i, s = 0;
  unsigned char val;
  union type_regs regs;

  regs.d32 = len;

  for (i = 0; i != len * MR_ITERATIONS; i++)
    {
      val = *(str + (i % len));

      regs.d8[s] =  sTable[(sTable[(val + i) & 255] + regs.d8[s]) & 255];

      s = (s + 1) % 4;

      regs.d32 = LROT1(regs.d32);
    }

  return regs.d32;
}



MurmurHash2AM

Как можно улучшить функцию, котороя итак уже близка к оптимальной? Легко. Надо добавить счетчик. Счетчик блока. Ведь счетчик вообще - дело хорошее. Он помогает нам для двух одинаковых блоков вычислить разные значения. Это не совсем то, что даст хорошая табличная подстановка, но уже избавит от множества проблем. Мы не получим таких всплесков на сложных наборах, как исходный вариант. При этом общие результаты пострадать не должны.

Итак, перед вами моя модификация MurmurHash2AM. M- потому, что модификация. AM, как ни странно - тоже имеет смысл - имя автора модификации.


#define mmix(h,k) { k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; }

unsigned int MurmurHash2AM ( char * key, unsigned int len)
{
  const unsigned int m = 0x5bd1e995;
  const int r = 24;
  unsigned int l = len, h = 0, i = 1;

  const unsigned char * data = (const unsigned char *)key;


  while (len >= 4)
    {
      unsigned int k = *(unsigned int*)data + i++;

      mmix(h,k);

      data += 4;
      len -= 4;
    }

  unsigned int t = 0;

  switch (len)
    {
    case 3:
      t ^= data[2] << 16;
    case 2:
      t ^= data[1] << 8;
    case 1:
      t ^= data[0];
    };

  mmix(h,t);
  mmix(h,l);

  h ^= h >> 13;
  h *= m;
  h ^= h >> 15;

  return h;

}



Итак, приступим к тестированию! Свыше двух миллионов уникальных текстов как в латинице, так и в кириллице обязательно вызовут коллизии. Вопрос только в том, сколько их будет и можно ли как-то превзойти результаты существующих функций? Для того, чтобы тестирование было объективным, проведем не один заход, а целую серию, изменяя входной текст на каждом шаге, постепенно усложняя задачу, чтобы “поймать” функцию на всплеске коллизий.

Примечание: в данном тесте в качестве функции fnv применяется распространенная в сети реализация. Официальный референсный код применяется в fnv1 и fnv1a алгоритмах.

Алгоритм Коллизии в тесте A Коллизии в тесте B Коллизии в тесте C Коллизии в тесте D Коллизии в тесте E Коллизии в тесте F Коллизии в тесте G Коллизии в тесте H Коллизии, среднее
MaHash8v64 0 0 0 0 0 0 0 0 0
maPrime2c 804 814 810 873 856 869 796 864 836
maPrime2d 844 833 812 801 808 918 835 877 841
maPrime 878 860 862 781 824 808 874 853 843
MaHash8 796 858 806 873 893 838 862 843 846
maPrime2 830 831 863 807 843 871 863 866 847
MaHash4 826 812 828 831 922 864 888 808 847
MaHashMR1 838 837 849 849 856 850 899 807 848
maPrime2a 822 859 766 916 842 814 880 901 850
maPrime2b 822 859 766 916 842 814 880 901 850
MaHash11 795 838 870 861 855 868 857 908 857
Lookup3 886 868 900 785 867 892 831 834 858
MaHash5 819 832 838 866 944 887 840 846 859
Murmur2AM 830 863 884 888 878 869 827 849 861
crc32 856 856 856 856 886 827 899 859 862
MaHash3 894 851 876 895 824 862 823 871 862
MaHash2 819 891 866 832 846 859 875 926 864
MaHash1 910 839 841 881 899 933 902 884 886
Murmur2a 848 847 922 836 990 1140 879 861 915
Faq6 947 947 947 947 889 869 906 913 921
OneAtTime 947 947 947 947 889 869 906 913 921
MaHash7 839 942 1097 1082 848 867 847 865 923
MaHash9 831 932 1114 1133 831 889 864 865 932
MaHash6 864 902 1077 1072 897 898 904 848 933
MaHash/3 823 928 1095 1260 869 885 857 892 951
MaHash7/3 846 938 1115 1189 866 877 892 909 954
MaHash 918 900 1191 1275 812 856 892 913 970
maBKDR 824 795 840 857 829 1728 894 1009 972
Murmur2 861 864 808 823 1138 1832 836 874 1005
MaHash10 839 1006 1346 1466 827 869 836 970 1020
ROT13 987 1016 1095 1129 1015 964 928 1459 1074
Murmur3 887 860 826 829 2097 2330 861 854 1193
AP 873 887 827 813 1416 4568 885 842 1389
LY 819 819 819 819 1748 3543 883 1766 1402
BKDR 860 860 860 860 1708 3548 894 1906 1437
fnv1a 807 807 807 807 2650 5235 910 1695 1715
fnv1 869 869 869 869 2589 5209 899 1758 1741
djb 1152 1152 1152 1152 2081 3787 1180 5952 2201
SBOX 1511 1521 1608 1594 2722 4940 1530 2308 2217
fnv 873 873 873 873 4436 9037 800 3386 2644
Rs 845 896 877 867 2092 9298 802 6375 2757
Crap8 851 875 870 886 9523 9633 861 868 3046
SuperFastHash 4987 3889 3934 3935 2066 1873 3862 968 3189
Js 3513 4151 6105 6735 1009 881 3350 898 3330
x31 3266 3227 3292 3231 4930 7414 2650 3053 3883
GoulburnHash 5309 9635 22197 26506 5218 5312 5380 5311 10609
Sdbm 943 943 943 943 14996 30196 869 89196 17379
x17 25281 25346 25254 25239 26179 27892 23828 1903 22615
Dek 6824 5474 5362 5070 24564 422012 8629 1820 59969
CrapWow 996 970 1006 996 397113 683161 1026 2757 136003
Elf 52291 268965 268965 268733 455236 706374 49808 70326 267587
PJW-32 52291 268965 268965 268733 455236 706374 49808 70326 267587
Fletcher 434075 30303 30349 36015 284334 282984 250407 2174482 440369
Novak 753734 753734 753734 753734 597509 598661 729428 34043 621822
Adler 1952330 1952330 1952330 1952330 1951871 1951872 1952044 2709710 2046852
bp 2276753 2596105 2726283 2726298 2279190 2279402 2274948 2274754 2429217

Проверим результаты теста на том же наборе данных, но в другой кодировке. Как поведут себя алгоритмы на тех же текстах, но в кодировке cp1251? Наши функции лидируют, хотя в отрыве теперь уже другие финалисты. Найти тестовые материалы можно здесь. Результаты в виде электронной таблицы доступны здесь.

Алгоритм Коллизии в тесте A Коллизии в тесте B Коллизии в тесте C Коллизии в тесте D Коллизии в тесте E Коллизии в тесте F Коллизии в тесте G Коллизии в тесте H Коллизии, среднее
MaHash8v64 0 0 0 0 0 0 0 0 0
MaHash3 853 803 871 835 834 871 835 828 841
maPrime2c 875 812 870 859 876 825 832 838 848
crc32 854 854 854 854 865 814 837 865 850
maPrime2d 838 860 831 816 854 864 886 860 851
MaHash4 821 882 826 855 877 856 853 854 853
maPrime 857 868 832 843 825 801 913 901 855
MaHash5 860 806 896 865 908 865 840 805 856
MaHash2 833 861 842 800 851 880 872 916 857
MaHashMR1 846 838 885 878 865 868 875 824 860
Lookup3 890 869 888 803 897 837 878 830 862
MaHash11 840 816 877 887 850 871 861 901 863
MaHash1 856 836 825 854 928 853 895 878 866
Murmur2AM 851 840 898 856 896 878 842 887 869
MaHash8 901 871 859 872 874 901 853 834 871
maPrime2 889 827 890 862 907 868 851 874 871
maPrime2a 883 919 839 902 838 849 864 882 872
maPrime2b 883 919 839 902 838 849 864 882 872
Murmur2a 842 894 852 867 986 1113 895 862 914
Faq6 950 950 950 950 884 871 922 894 921
OneAtTime 950 950 950 950 884 871 922 894 921
MaHash7/3 824 860 1055 1152 890 909 877 878 931
MaHash9 900 990 1114 1137 787 853 846 866 937
MaHash6 876 942 1111 1187 905 859 835 865 948
MaHash/3 844 950 1156 1172 856 890 899 874 955
MaHash7 882 933 1127 1197 875 855 921 859 956
MaHash 887 931 1163 1249 884 915 878 916 978
maBKDR 899 864 801 884 857 1660 848 1091 988
Murmur2 809 821 823 843 1089 1934 831 872 1003
MaHash10 853 1053 1309 1429 823 897 893 963 1028
ROT13 952 982 1070 1079 1070 913 944 1463 1059
Murmur3 838 822 833 865 2076 2390 889 868 1198
LY 798 798 798 798 1750 3387 861 1747 1367
AP 890 844 867 890 1424 4564 924 855 1407
BKDR 867 867 867 867 1677 3402 847 1941 1417
fnv1a 849 849 849 849 2621 5152 832 1740 1718
fnv1 881 881 881 881 2689 5303 898 1725 1767
SBOX 1568 1516 1584 1502 2764 4911 1543 2333 2215
fnv 867 867 867 867 4608 8993 895 3391 2669
Rs 869 877 945 839 2037 9292 783 6524 2771
Crap8 869 911 866 869 9358 9538 914 909 3029
SuperFastHash 4824 3868 3869 3932 1975 1866 3825 947 3138
Js 3450 4114 6108 6577 983 907 3298 885 3290
x31 3139 3092 3142 3122 4903 7454 2567 3622 3880
djb 1216 1216 1216 1216 24811 3740 1184 6003 5075
GoulburnHash 5243 9510 22240 26600 5131 5434 5375 5264 10600
Sdbm 897 897 897 897 14995 29758 825 89227 17299
x17 24994 25125 25068 24998 25948 27636 23828 1937 22442
Dek 6403 5905 5685 5347 24811 422429 10995 1862 60430
CrapWow 1018 978 1005 1003 397114 683147 997 2791 136007
PJW-32 51274 261719 261719 261271 428199 650498 49215 63792 253461
Fletcher 441807 32747 32784 40013 283951 284270 259720 2187973 445408
Elf 51274 261719 261719 261271 2279198 650498 49215 63792 484836
Novak 738325 738325 738325 738325 607665 608812 713049 34024 614606
Adler 1959011 1959011 1959011 1959011 1958760 1958780 1959042 2708057 2052585
bp 2276057 2596217 2726283 2726298 2279198 2279410 2274416 2274878 2429095

Существует довольно известный тест от Сергея Вакуленко, где используется сокращенный набор словарного материала (у меня получилось 326797 слова, результаты теста A совпадают с результатами Сергея). Найти тестовые материалы можно здесь. Результаты в виде электронной таблицы доступны здесь.

Алгоритм Коллизии в тесте A Коллизии в тесте B Коллизии в тесте C Коллизии в тесте D Коллизии в тесте E Коллизии в тесте F Коллизии в тесте G Коллизии в тесте H Коллизии, среднее
MaHash8v64 0 0 0 0 0 0 0 0 0
MaHash4 4 12 8 8 17 13 14 7 10
MaHash1 10 12 10 11 10 17 7 10 11
MaHash6 10 10 11 13 12 13 9 10 11
MaHash3 11 14 12 10 9 14 9 9 11
crc32 13 13 13 13 8 11 11 6 11
MaHash2 11 10 14 10 8 12 10 14 11
MaHash5 7 10 9 12 23 11 10 11 12
MaHash8 7 14 11 12 12 9 13 17 12
ROT13 7 9 11 13 8 14 13 22 12
MaHash11 10 13 12 16 9 13 14 12 12
Lookup3 9 13 21 12 8 7 11 19 13
maPrime 12 11 8 16 17 11 11 14 13
Murmur2AM 7 15 16 17 11 13 6 16 13
MaHash7 10 12 9 22 11 15 11 11 13
MaHash 11 14 15 19 12 8 14 10 13
Faq6 14 14 14 14 8 14 15 11 13
OneAtTime 14 14 14 14 8 14 15 11 13
maPrime2c 17 6 12 6 17 26 11 9 13
MaHash/3 14 11 11 17 10 11 10 21 13
MaHashMR1 11 7 14 20 15 10 19 12 14
Murmur2a 18 13 13 15 20 7 6 17 14
maPrime2d 16 14 12 18 11 12 15 12 14
MaHash9 20 12 19 12 16 9 10 12 14
maBKDR 14 14 14 12 12 23 16 9 14
MaHash7/3 15 23 22 14 12 11 6 14 15
Murmur2 9 22 15 14 12 25 11 13 15
MaHash10 14 18 11 23 15 14 21 11 16
Murmur3 17 12 11 19 33 45 11 6 19
LY 9 9 9 9 25 61 10 24 20
maPrime2 32 16 17 13 12 16 37 13 20
BKDR 11 11 11 11 26 51 14 29 21
maPrime2a 31 15 13 13 15 15 50 14 21
maPrime2b 31 15 13 13 15 15 50 14 21
AP 16 19 8 13 26 76 16 13 23
fnv1a 6 6 6 6 49 77 17 27 24
fnv1 13 13 13 13 47 75 7 27 26
SBOX 26 29 22 22 42 66 23 37 33
rs 9 8 14 16 31 134 18 77 38
fnv 10 10 10 10 58 151 8 54 39
SuperFastHash 58 53 59 70 43 31 82 12 51
Js 98 110 145 145 20 20 100 15 82
djb 84 84 84 84 100 128 79 31 84
x31 85 84 86 81 114 142 67 23 85
Crap8 16 14 13 12 444 450 19 15 123
GoulburnHash 69 135 292 371 82 88 74 90 150
Sdbm 13 13 13 13 216 429 13 949 207
x17 417 423 422 420 437 457 469 19 383
Dek 308 251 233 214 3271 41670 295 149 5799
Elf 1315 4375 4375 4341 23838 35022 1778 999 9505
PJW-32 1315 4375 4375 4341 23838 35022 1778 999 9505
Fletcher 12950 214 210 217 2983 2915 15101 67551 12768
Novak 16194 16194 16194 16194 11482 11511 14958 588 12914
CrapWow 73 71 63 73 42618 81664 136 110 15601
Adler 53114 53114 53114 53114 52896 52927 53115 286829 82278
bp 254849 290245 326781 326796 255208 255229 250380 250875 276295

Проведем еще один тест от Сергея Вакуленко, на втором тестовом наборе. Здесь используется сокращенный набор словарного материала (у меня получилось 310595 слов, результаты теста A - это аналог второго теста Сергея). Найти тестовые материалы можно здесь. Результаты в виде электронной таблицы доступны здесь.

Алгоритм Коллизии в тесте A Коллизии в тесте B Коллизии в тесте C Коллизии в тесте D Коллизии в тесте E Коллизии в тесте F Коллизии в тесте G Коллизии в тесте H Коллизии, среднее
MaHash8v64 0 0 0 0 0 0 0 0 0
MaHash3 8 12 6 5 11 13 8 7 9
Murmur2a 5 8 9 9 10 9 17 4 9
Murmur2AM 8 11 10 11 10 8 7 8 9
MaHash5 9 11 7 5 13 10 19 5 10
maPrime2c 10 9 11 7 14 10 12 8 10
MaHash11 10 17 15 12 9 5 4 10 10
MaHash1 10 7 10 11 6 13 15 13 11
MaHash2 11 12 9 10 12 9 15 7 11
maPrime 7 13 11 9 12 10 8 16 11
MaHash6 9 8 11 9 12 11 15 11 11
MaHash8 10 9 15 7 8 13 13 12 11
lookup3 13 12 5 12 13 8 11 13 11
MaHash 6 12 15 22 13 4 7 11 11
maPrime2d 12 15 11 12 9 12 15 5 11
maBKDR 16 9 12 9 8 20 8 12 12
ROT13 9 9 10 10 15 17 9 16 12
MaHash9 6 9 11 14 12 13 12 19 12
faq6 12 12 12 12 12 13 17 10 13
OneAtTime 12 12 12 12 12 13 17 10 13
MaHash4 8 11 12 12 14 17 15 12 13
maHashMR1 8 12 12 10 16 10 20 14 13
crc32 14 14 14 14 9 12 11 14 13
MaHash7 13 13 15 15 10 8 15 14 13
MaHash10 13 14 18 20 12 12 10 7 13
Murmur2 13 15 12 11 12 25 12 10 14
maPrime2 22 11 6 8 4 13 39 13 15
LY 7 7 7 7 14 45 12 25 16
maPrime2a 29 10 14 11 13 13 36 12 17
maPrime2b 29 10 14 11 13 13 36 12 17
Murmur3 6 15 15 15 38 37 8 12 18
BKDR 13 13 13 13 20 47 11 20 19
FNV1a 8 8 8 8 27 68 6 31 21
FNV1 13 13 13 13 36 80 8 16 24
SBOX 24 20 21 22 33 54 27 45 31
AP 31 34 32 34 21 67 23 12 32
RS 4 5 11 8 24 120 18 82 34
FNV 14 14 14 14 57 111 6 55 36
SuperFastHash 50 37 38 48 31 20 72 24 40
Crap8 7 23 14 16 321 315 11 13 90
JS 145 153 174 181 27 17 149 11 107
Goulburn 85 133 293 362 82 77 70 57 145
x31 166 160 155 159 175 207 158 34 152
DJB 185 185 185 185 203 231 175 55 176
SDBM 11 11 11 11 175 357 16 879 184
x17 596 594 594 595 610 629 638 11 533
DEK 775 723 694 687 1233 40424 569 57 5645
ELF 1407 4512 4512 4421 33592 55251 1353 888 13242
PJW-32 1407 4512 4512 4421 33592 55251 1353 888 13242
CrapWow 13 8 10 14 39672 76255 7 56 14504
Novak 19964 19964 19964 19964 16069 16092 17420 476 16239
Fletcher 26693 296 301 296 2489 2463 15668 83045 16406
Adler 45463 45463 45463 45463 42930 42822 49032 297984 76828
BP 244709 285070 310579 310594 245339 245360 237688 238272 264701

Проведем еще один тест, теперь уже на случаных данных. Тестовый набор - упоррядоченные по возрастанию строки, состоящие из неосмысленного набора латиинских символов и различных знаков, которые получены с помощью генератора псевдослучайных чисел (всего полчилось 2724448 уникальных строк). Найти тестовые материалы можно здесь. Результаты в виде электронной таблицы доступны здесь.

Алгоритм Коллизии в тесте A Коллизии в тесте B Коллизии в тесте C Коллизии в тесте D Коллизии в тесте E Коллизии в тесте F Коллизии в тесте G Коллизии в тесте H Коллизии, среднее
MaHash8v64 0 0 0 0 0 0 0 0 0
maPrime 806 820 850 839 832 834 864 897 843
MaHash8 834 853 851 831 837 868 846 844 846
Lookup3 890 864 848 841 847 832 828 873 853
MaHash4 861 842 872 895 857 855 853 854 861
MaHash2 886 853 858 892 839 889 837 843 862
Murmur2AM 893 824 849 857 918 862 881 823 863
maPrime2c 833 863 871 861 872 878 858 875 864
MaHash1 885 821 855 930 821 884 862 855 864
MaHash3 830 875 890 856 904 847 816 897 864
maPrime2d 877 841 846 893 837 880 888 858 865
maPrime2 820 859 870 890 855 917 877 835 865
Murmur2a 829 833 863 898 872 858 904 872 866
MaHash5 895 872 889 813 841 870 891 873 868
MaHash7 846 891 885 896 886 833 852 869 870
Faq6 880 880 880 880 855 887 843 853 870
OneAtTime 880 880 880 880 855 887 843 853 870
MaHash11 867 876 846 864 928 877 846 865 871
maPrime2a 845 891 821 871 848 883 965 877 875
maPrime2b 845 891 821 871 848 883 965 877 875
crc32 882 882 882 882 880 874 869 865 877
MaHashMR1 911 897 903 858 870 855 886 840 878
Crap8 876 857 841 855 920 944 832 898 878
MaHash10 878 897 890 997 870 813 849 843 880
MaHash6 821 896 941 932 898 855 851 849 880
MaHash 920 901 930 964 872 876 869 876 901
MaHash9 902 901 963 935 878 864 910 887 905
ROT13 888 911 984 1022 910 857 879 1087 942
maBKDR 873 886 901 851 878 1704 856 820 971
Murmur2 866 845 828 870 1079 1859 865 865 1010
SuperFastHash 988 1002 949 960 1021 1247 963 2213 1168
Murmur3 889 826 845 893 2117 2139 869 801 1172
LY 822 822 822 822 1644 3465 863 1780 1380
BKDR 832 832 832 832 1664 3531 871 1655 1381
AP 867 861 871 855 1543 4951 892 876 1465
fnv1a 857 857 857 857 2668 5204 911 1673 1736
fnv1 889 889 889 889 2574 5095 870 1823 1740
SBOX 1550 1516 1542 1586 2681 4875 1552 2256 2195
fnv 862 862 862 862 4525 8844 850 3453 2640
Rs 839 854 915 865 2026 9200 854 6495 2756
Js 12769 13431 15301 15842 1479 838 12964 819 9180
GoulburnHash 5174 9438 21959 26284 5153 5116 5243 5253 10453
djb 11565 11565 11565 11565 12340 14002 11706 1860 10771
x31 12829 12803 12804 12786 14173 16165 12907 1738 12026
Sdbm 901 901 901 901 15405 30041 885 57217 13394
x17 32369 32332 32338 32361 33265 34996 32192 1734 28948
Dek 12084 12053 12093 12049 119473 368879 12107 920 68707
CrapWow 907 864 853 857 351663 702929 865 863 132475
Fletcher 43725 841 871 895 55367 55265 43769 929201 141242
Novak 176178 176178 176178 176178 149533 151574 176405 46132 153545
Elf 47250 237952 237952 237883 212765 333799 47297 14059 171120
PJW-32 47250 237952 237952 237883 212765 333799 47297 14059 171120
Adler 222626 222626 222626 222626 148843 140574 222285 2668138 508793
bp 3301 285883 2548867 2724447 3456 3456 3257 3387 697007

Контрольный тест на случаных данных - теперь немного более жесткий, тексты длиннее. Тестовый набор - упорядоченные по возрастанию строки, состоящие из неосмысленного набора латиинских и кириллистических символов (в кодировке cp1251), цифр, которые получены с помощью генератора псевдослучайных чисел (всего полчилось 2725573 уникальных строк, длиной от 2 до 128 символов). Найти тестовые материалы можно здесь. Результаты в виде электронной таблицы доступны здесь.

Алгоритм Коллизии в тесте A Коллизии в тесте B Коллизии в тесте C Коллизии в тесте D Коллизии в тесте E Коллизии в тесте F Коллизии в тесте G Коллизии в тесте H Коллизии, среднее
MaHash8v64 0 0 0 0 0 0 0 0 0
MaHash8 848 850 845 861 856 829 856 827 847
Murmur2AM 837 841 845 855 869 858 823 857 848
MaHash11 838 866 866 855 795 850 914 822 851
faq6 843 843 843 843 845 882 844 885 854
OneAtTime 843 843 843 843 845 882 844 885 854
MaHash5 885 878 804 858 871 794 851 894 854
maPrime2c 899 833 839 819 842 901 831 891 857
maHashMR1 869 878 808 846 861 830 881 886 857
lookup3 833 886 836 817 889 889 870 851 859
maPrime2d 866 843 847 879 869 885 871 830 861
MaHash2 856 876 894 907 840 841 830 861 863
maPrime2a 868 853 885 853 822 905 871 858 864
maPrime2b 868 853 885 853 822 905 871 858 864
MaHash1 904 859 896 861 880 873 844 830 868
MaHash4 875 903 880 863 820 858 880 885 871
MaHash9 856 859 897 918 858 866 842 869 871
Murmur2a 907 867 831 844 865 893 881 887 872
MaHash3 914 888 850 843 896 868 855 870 873
crc32 850 850 850 850 924 935 835 900 874
maPrime2 894 890 878 892 880 850 852 859 874
maPrime 873 858 905 892 821 850 909 890 875
MaHash 842 824 911 915 898 866 868 877 875
Crap8 877 911 855 863 899 947 868 844 883
MaHash7 937 882 868 942 848 892 872 856 887
MaHash6 876 931 897 923 881 841 879 870 887
MaHash10 881 872 913 1001 920 857 806 870 890
ROT13 907 931 1022 1024 897 899 919 961 945
maBKDR 891 938 847 864 820 1759 861 886 983
Murmur2 901 812 817 827 1092 1933 885 884 1019
SuperFastHash 943 955 1002 989 1096 1275 979 1754 1124
Murmur3 877 885 860 856 2144 2236 861 889 1201
LY 861 861 861 861 1725 3409 849 1741 1396
BKDR 889 889 889 889 1718 3386 859 1786 1413
FNV1 825 825 825 825 2571 5241 885 1716 1714
FNV1a 884 884 884 884 2597 5173 918 1752 1747
AP 1425 1435 1391 1392 1562 4926 1431 854 1802
SBOX 1566 1500 1518 1570 2689 4739 1509 2302 2174
FNV 927 927 927 927 4597 9030 828 3754 2740
RS 865 892 820 851 1984 9250 888 6524 2759
JS 9684 10361 12301 12861 1180 884 9671 837 7222
DJB 9768 9768 9768 9768 10575 12266 9782 1687 9173
x31 10644 10702 10610 10637 11946 14061 10609 1702 10114
Goulburn 5240 9647 22254 26050 5127 5202 5214 5089 10478
SDBM 911 911 911 911 15040 30057 859 54841 13055
x17 25020 25017 24953 24982 25872 27546 25080 1684 22519
DEK 10477 10590 10555 10569 118188 367491 10628 903 67425
ELF 40318 232079 232079 232100 96275 146751 40854 14176 129329
PJW-32 40318 232079 232079 232100 96275 146751 40854 14176 129329
CrapWow 859 862 904 865 351728 702936 842 840 132480
Fletcher 38147 883 872 901 54438 54905 38117 911473 137467
Novak 177615 177615 177615 177615 153819 155608 177681 48437 155751
Adler 52773 52773 52773 52773 33169 30881 52937 2586412 364311
BP 2580 257610 2725557 2725572 2651 2651 2626 2523 715221

Еще один жесткий тест на случаных данных - синтетический, на однотипных текстах с ограниченным набором символов. Тестовый набор - упорядоченные по возрастанию строки, состоящие из неосмысленного набора латиинских символов в верхнем регистре, причем строки имеют частичные совпадения (всего полчилось 2690227 уникальных строк, длиной от 3 до 64 символов). Найти тестовые материалы можно здесь. Результаты в виде электронной таблицы доступны здесь.

Алгоритм Коллизии в тесте A Коллизии в тесте B Коллизии в тесте C Коллизии в тесте D Коллизии в тесте E Коллизии в тесте F Коллизии в тесте G Коллизии в тесте H Коллизии, среднее
MaHash8v64 0 0 0 0 0 0 0 0 0
maHashMR1 797 830 818 818 820 847 771 846 818
MaHash8 825 820 807 871 830 867 801 804 828
MaHash5 852 832 787 818 825 824 834 870 830
MaHash2 873 819 870 867 794 811 846 795 834
lookup3 889 799 793 814 854 872 855 802 835
maPrime2 807 821 867 817 803 829 884 860 836
MaHash11 878 838 850 809 852 773 869 846 839
MaHash1 859 818 847 842 822 820 849 868 841
MaHash4 826 809 865 838 889 853 809 843 842
maPrime2c 832 838 856 813 864 859 806 877 843
MaHash3 756 829 885 883 860 829 871 835 844
maPrime2a 822 836 824 847 836 857 880 852 844
maPrime2b 822 836 824 847 836 857 880 852 844
maPrime2d 820 863 825 841 859 838 840 883 846
maPrime 837 853 834 844 886 886 857 785 848
MaHash6 829 849 900 858 836 864 866 791 849
Murmur2a 821 827 802 881 866 885 909 808 850
MaHash7 860 857 881 879 820 779 860 868 851
crc32 856 856 856 856 871 826 828 858 851
MaHash9 843 852 903 905 874 875 812 787 856
Murmur2AM 895 826 860 855 858 855 823 894 858
MaHash 854 876 891 923 879 814 848 851 867
faq6 923 923 923 923 853 800 828 831 876
OneAtTime 923 923 923 923 853 800 828 831 876
MaHash10 811 834 943 927 884 858 856 893 876
ROT13 876 899 966 999 949 883 889 982 930
maBKDR 850 869 798 854 843 1608 849 845 940
Murmur2 876 882 858 808 1072 1821 856 830 1000
SuperFastHash 1053 1008 1041 1006 1020 1297 1071 911 1051
Murmur3 799 841 893 840 1734 1939 865 878 1099
Crap8 831 795 868 855 2613 2662 847 816 1286
LY 840 840 840 840 1737 3315 829 1694 1367
BKDR 876 876 876 876 1683 3425 801 1774 1398
AP 813 822 852 834 1546 4861 852 855 1429
DJB 840 840 840 840 1649 3340 852 2488 1461
x31 815 906 912 822 2080 4189 832 1810 1546
FNV1 841 841 841 841 2495 5050 815 1655 1672
FNV1a 833 833 833 833 2592 5201 802 1711 1705
SBOX 1456 1485 1533 1526 2637 4845 1516 2277 2159
JS 2261 2871 4717 5291 877 844 2225 920 2501
FNV 872 872 872 872 4374 8873 881 3301 2615
RS 789 838 889 854 2006 8868 851 6333 2679
Goulburn 5114 9120 21383 25464 5120 5095 5160 5106 10195
SDBM 817 817 817 817 14471 28892 855 61316 13600
x17 16230 16279 16265 16297 17076 18732 16191 1727 14850
DEK 947 954 919 916 112745 356921 970 977 59419
Novak 111385 111385 111385 111385 92589 94547 111460 17383 95190
CrapWow 963 993 991 996 351443 700750 975 907 132252
Fletcher 30522 2375 2359 4453 75912 76548 30573 1193428 177021
ELF 31966 219295 219295 218834 294173 462650 32056 22791 187633
PJW-32 31966 219295 219295 218834 294173 462650 32056 22791 187633
Adler 522275 522275 522275 522275 513410 513397 521984 2665529 787928
BP 1118198 2411300 2690211 2690226 1128132 1128132 1120296 1128264 1676845

Усложняем тест на случаных данных на текстах, имеющих частичные совпадения. Тестовый набор - упорядоченные по возрастанию строки, состоящие из неосмысленного набора латиинских символов в верхнем и нижнем регистре, а так же чисел (всего полчилось 2724332 уникальных строк, длиной от 3 до 253 символов). Найти тестовые материалы можно здесь. Результаты в виде электронной таблицы доступны здесь.

Алгоритм Коллизии в тесте A Коллизии в тесте B Коллизии в тесте C Коллизии в тесте D Коллизии в тесте E Коллизии в тесте F Коллизии в тесте G Коллизии в тесте H Коллизии, среднее
MaHash8v64 0 0 0 0 0 0 0 0 0
MaHash1 834 888 835 877 848 805 836 833 845
Murmur2a 858 825 881 811 823 876 851 884 851
MaHash6 826 824 828 874 916 861 846 849 853
MaHash4 839 821 785 910 880 835 895 867 854
maPrime2 832 842 862 886 862 864 830 885 858
MaHash2 867 860 912 848 878 854 833 855 863
maPrime2a 903 865 852 893 820 894 845 836 864
maPrime2b 903 865 852 893 820 894 845 836 864
Murmur2AM 870 859 902 868 890 852 825 853 865
lookup3 874 864 826 886 856 889 867 860 865
MaHash10 849 863 966 870 906 787 807 880 866
MaHash11 930 861 870 899 848 807 887 834 867
crc32 877 877 877 877 826 878 845 883 868
maPrime2c 837 870 878 889 888 879 842 868 869
MaHash8 849 856 862 876 825 878 897 912 869
MaHash3 868 852 906 854 891 928 862 810 871
MaHash5 914 865 871 855 853 841 873 904 872
maHashMR1 864 871 825 869 913 876 899 862 872
maPrime 859 836 899 872 845 904 863 905 873
MaHash 882 877 829 902 871 904 840 913 877
Crap8 877 891 917 842 871 880 899 856 879
MaHash7 868 904 923 872 863 873 878 871 882
MaHash9 912 857 857 942 898 858 885 847 882
maPrime2d 910 877 889 866 868 897 851 918 885
faq6 903 903 903 903 837 842 904 910 888
OneAtTime 903 903 903 903 837 842 904 910 888
ROT13 912 937 1011 1035 936 855 906 927 940
Murmur3 850 913 845 868 1027 1365 862 914 956
maBKDR 887 830 815 888 839 1666 893 913 966
SuperFastHash 971 944 964 924 1031 1020 980 1110 993
Murmur2 895 884 816 851 1113 1844 877 902 1023
BKDR 841 841 841 841 1688 3481 828 1658 1377
LY 891 891 891 891 1752 3427 806 1690 1405
AP 838 877 856 866 1453 4996 927 890 1463
FNV1a 868 868 868 868 2604 5213 882 1721 1737
FNV1 863 863 863 863 2653 5225 826 1748 1738
SBOX 1581 1546 1508 1556 2814 4900 1527 2224 2207
DJB 1842 1842 1842 1842 2676 4492 1920 1656 2264
JS 1844 2511 4524 4997 935 882 1903 862 2307
x31 1938 1917 1935 1922 3244 5440 1958 1675 2504
FNV 837 837 837 837 4487 8929 894 3501 2645
RS 866 894 880 866 1992 9265 868 7288 2865
x17 3821 3818 3772 3775 4668 6368 3826 1697 3968
Goulburn 5122 9298 21945 26004 5190 5190 5233 5352 10417
SDBM 825 825 825 825 15187 30151 824 54488 12994
Novak 21645 21645 21645 21645 18381 20351 21631 6567 19189
DEK 1955 1974 1945 1931 85310 335483 1921 873 53924
Fletcher 9639 874 843 857 55675 55338 9600 592448 90659
ELF 18528 212242 212242 212649 71787 112449 18759 15559 109277
PJW-32 18528 212242 212242 212649 71787 112449 18759 15559 109277
CrapWow 877 857 905 813 336727 684855 883 857 128347
Adler 16954 16954 16954 16954 13136 12908 16854 2370151 310108
BP 1113890 1445770 2724316 2724331 1114160 1114160 1114126 1114432 1558148

Немного другой вариант теста - ограничим его пространством русских текстов. Тестовый набор - упорядоченные по возрастанию строки, состоящие из неосмысленного набора кириллических символов в верхнем и нижнем регистре (всего получилось 2724012 уникальных строки, длиной от 3 до 253 символов). Найти тестовые материалы можно здесь. Результаты в виде электронной таблицы доступны здесь.

Алгоритм Коллизии в тесте A Коллизии в тесте B Коллизии в тесте C Коллизии в тесте D Коллизии в тесте E Коллизии в тесте F Коллизии в тесте G Коллизии в тесте H Коллизии, среднее
MaHash8v64 0 0 0 0 0 0 0 0 0
MaHash1 874 793 838 833 860 828 893 869 849
maPrime2a 803 833 912 858 854 869 841 845 852
maPrime2b 803 833 912 858 854 869 841 845 852
maPrime2 871 853 808 806 860 887 858 890 854
Murmur2a 840 843 827 870 892 879 832 853 855
maPrime2d 826 802 886 890 904 896 860 801 858
maPrime 859 846 863 851 828 884 875 861 858
maHashMR1 895 873 896 815 840 827 895 845 861
MaHash11 886 870 868 825 922 860 855 804 861
maPrime2c 867 919 866 812 827 821 880 919 864
crc32 857 857 857 857 866 870 835 913 864
MaHash7 857 885 846 870 910 891 840 820 865
MaHash9 906 829 878 950 871 806 847 845 867
MaHash4 923 859 864 871 848 855 863 849 867
MaHash5 874 798 859 862 854 897 922 868 867
lookup3 874 859 935 894 816 864 832 864 867
MaHash 871 829 915 871 879 908 859 811 868
Crap8 896 877 861 805 904 879 877 844 868
MaHash8 859 830 873 880 893 857 883 872 868
MaHash3 918 875 855 852 833 883 898 844 870
MaHash2 848 923 881 911 833 905 852 829 873
faq6 873 873 873 873 859 908 877 882 877
OneAtTime 873 873 873 873 859 908 877 882 877
Murmur2AM 850 902 848 827 925 904 874 896 878
MaHash6 866 842 924 849 889 856 915 896 880
MaHash10 900 888 845 880 848 892 915 870 880
ROT13 857 891 971 988 865 927 926 910 917
Murmur3 877 890 844 879 1047 1329 860 876 950
maBKDR 868 893 885 882 886 1598 872 819 963
SuperFastHash 974 983 948 957 970 951 987 1043 977
Murmur2 875 850 870 820 1098 1916 841 829 1012
LY 861 861 861 861 1766 3517 807 1734 1409
BKDR 874 874 874 874 1693 3547 863 1710 1414
AP 886 899 959 975 1402 4905 857 855 1467
FNV1 837 837 837 837 2475 5032 852 1711 1677
FNV1a 899 899 899 899 2612 5119 868 1724 1740
DJB 1441 1441 1441 1441 2327 3998 1415 1794 1912
JS 1516 2161 4057 4739 942 875 1499 890 2085
x31 1475 1524 1494 1476 2734 4998 1537 1762 2125
SBOX 1513 1560 1579 1565 2711 4877 1501 2344 2206
FNV 846 846 846 846 4516 9011 914 3446 2659
RS 846 855 872 829 2133 8986 848 7345 2839
x17 3344 3400 3336 3373 4280 6033 3293 1673 3592
Goulburn 5251 9446 21981 26409 5241 5217 5137 5045 10466
SDBM 859 859 859 859 14953 30026 868 54764 13006
Novak 20942 20942 20942 20942 18447 20553 20971 6482 18778
DEK 1389 1423 1405 1468 84158 335120 1463 911 53417
Fletcher 9593 850 883 865 55310 55398 9602 595436 90992
ELF 16591 211811 211811 212637 20258 26499 16287 13874 91221
PJW-32 16591 211811 211811 212637 20258 26499 16287 13874 91221
CrapWow 854 851 809 836 337043 683355 919 880 128193
Adler 15726 15726 15726 15726 15739 15912 15744 2416877 315897
BP 1199775 1656149 2723996 2724011 1199820 1199820 1200286 1200370 1638028

Расширим наш тест на случайных данных - теперь он включает практически весь набор возможных печатных символов. Тестовый набор - упорядоченные по возрастанию строки, состоящие из неосмысленного набора латинских, кириллических символов в верхнем и нижнем регистре, цифр, знаков (всего получилось 2724564 уникальных строки, длиной от 3 до 253 символов). Найти тестовые материалы можно здесь. Результаты в виде электронной таблицы доступны здесь.

Алгоритм Коллизии в тесте A Коллизии в тесте B Коллизии в тесте C Коллизии в тесте D Коллизии в тесте E Коллизии в тесте F Коллизии в тесте G Коллизии в тесте H Коллизии, среднее
MaHash8v64 0 0 0 0 0 0 0 0 0
maPrime2 878 851 867 848 829 813 816 853 844
MaHash8 837 873 832 852 827 839 944 828 854
MaHash5 856 872 801 848 850 884 877 848 855
Murmur2AM 884 833 863 827 835 902 869 840 857
Murmur2a 865 817 835 847 874 854 852 917 858
MaHash10 843 862 867 888 871 820 890 825 858
MaHash7 833 852 856 887 903 817 879 848 859
lookup3 869 870 886 846 909 828 844 831 860
MaHash11 847 856 931 855 876 825 845 859 862
maPrime2d 838 824 865 886 882 921 821 866 863
MaHash4 914 875 886 806 880 899 790 877 866
crc32 876 876 876 876 862 870 876 817 866
maPrime 868 858 885 846 834 879 879 900 869
MaHash9 872 864 919 884 799 818 916 885 870
maPrime2c 889 888 844 887 874 835 853 895 871
MaHash3 906 830 861 891 893 881 887 833 873
maPrime2a 834 840 885 884 887 850 910 894 873
maPrime2b 834 840 885 884 887 850 910 894 873
MaHash2 841 903 888 913 901 802 863 877 874
Crap8 850 821 933 903 855 859 878 893 874
MaHash6 868 894 843 900 856 866 890 876 874
faq6 887 887 887 887 861 858 871 863 875
OneAtTime 887 887 887 887 861 858 871 863 875
MaHash 922 892 875 841 864 832 881 894 875
MaHash1 925 882 855 881 913 862 868 868 882
maHashMR1 891 858 927 873 871 878 885 875 882
ROT13 898 930 1022 1038 903 880 892 886 931
Murmur3 849 874 877 861 1019 1330 851 829 936
SuperFastHash 918 945 963 909 1016 978 949 971 956
maBKDR 866 886 857 893 820 1788 857 852 977
Murmur2 843 897 914 900 1101 1806 824 892 1022
BKDR 933 933 933 933 1723 3473 887 1777 1449
LY 932 932 932 932 1748 3596 870 1793 1467
AP 898 902 873 879 1514 4994 850 838 1469
FNV1 843 843 843 843 2652 5182 866 1649 1715
DJB 1280 1280 1280 1280 2085 3849 1151 1678 1735
FNV1a 884 884 884 884 2603 5227 857 1729 1744
JS 1241 1859 3725 4442 906 861 1193 872 1887
x31 1232 1211 1258 1213 2513 4729 1220 1801 1897
SBOX 1595 1484 1471 1574 2685 4982 1522 2432 2218
x17 2049 1996 2089 2055 2870 4557 1998 1699 2414
FNV 904 904 904 904 4452 9012 866 3451 2675
RS 866 885 891 838 2029 9416 871 7228 2878
Goulburn 5132 9309 22013 26229 5266 5199 5140 5236 10441
SDBM 848 848 848 848 15251 29952 820 54526 12993
Novak 21251 21251 21251 21251 18445 20528 21143 6616 18967
DEK 1230 1202 1224 1218 83876 334433 1257 864 53163
Fletcher 4888 837 826 849 54876 55063 4753 590556 89081
ELF 15365 210114 210114 210950 22330 33341 15384 13981 91447
PJW-32 15365 210114 210114 210950 22330 33341 15384 13981 91447
CrapWow 871 888 833 852 336398 683930 898 871 128193
Adler 3953 3953 3953 3953 2820 2567 3922 1840427 233194
BP 1107319 1198700 2724548 2724563 1107368 1107368 1107640 1107656 1523145

Проведем еще один тест, правда вне конкурса. Тестовый набор - упорядоченные по возрастанию числа в десятичной системе исчисления. Очевидно, что здесь простые алгоритмы, основанные на математических вычислениях, покажут идеальные результаты. Наши алгоритмы не зависимы от типа исходного материала и покажут примерно те же результаты, что и на осмысленных текстах с небольшим допуском. Найти тестовые материалы можно здесь. Результаты в виде электронной таблицы доступны здесь.

Алгоритм Коллизии в тесте A Коллизии в тесте B Коллизии в тесте C Коллизии в тесте D Коллизии в тесте E Коллизии в тесте F Коллизии в тесте G Коллизии в тесте H Коллизии, среднее
MaHash8v64 0 0 0 0 0 0 0 0 0
maBKDR 0 0 0 0 83 162 0 357 75
crc32 0 0 0 0 428 414 0 419 158
LY 0 0 0 0 831 1704 0 854 424
x17 0 0 0 0 838 1651 0 1128 452
BKDR 0 0 0 0 868 1716 0 1129 464
Murmur2 415 798 876 835 866 1153 299 815 757
maPrime2a 816 792 749 751 964 755 834 801 808
maPrime2b 816 792 749 751 964 755 834 801 808
maPrime2 792 799 805 856 864 892 789 890 836
MaHash8 811 821 850 863 826 904 868 831 847
MaHash2 875 859 807 845 795 927 857 831 850
MaHashMR1 814 820 888 823 889 898 805 860 850
maPrime2d 818 884 915 839 878 814 825 851 853
maPrime2c 830 860 841 898 867 852 801 913 858
Murmur2AM 854 819 854 880 874 922 859 828 861
Lookup3 847 877 871 880 898 823 859 857 864
MaHash3 837 913 915 872 871 860 818 827 864
maPrime 832 906 880 941 864 865 808 826 865
MaHash11 870 877 879 840 908 849 880 849 869
MaHash5 877 851 871 835 880 886 883 886 871
MaHash1 900 844 850 871 906 857 882 861 871
Murmur3 848 932 836 889 805 1049 835 835 879
Murmur2a 818 822 801 887 833 1132 887 893 884
MaHash4 911 937 902 862 838 874 934 839 887
MaHash7 908 1221 2063 2435 849 890 1034 1053 1307
MaHash6 950 1220 2100 2374 878 942 1021 1056 1318
Rs 445 446 387 417 836 6623 410 1340 1363
MaHash9 906 1261 2241 2585 914 898 1083 1041 1366
fnv1 462 462 462 462 2282 4771 863 1800 1446
MaHash 855 1258 2545 2953 962 955 1077 1093 1462
fnv1a 736 736 736 736 2326 4639 705 1761 1547
Faq6 1894 1894 1894 1894 905 876 2548 908 1602
OneAtTime 1894 1894 1894 1894 905 876 2548 908 1602
MaHash10 1117 1796 3640 4310 1059 972 1180 1290 1921
Crap8 1134 1050 1071 1085 4760 4993 1379 812 2036
SBOX 1470 1545 1539 1496 2643 4821 1465 2312 2161
fnv 1492 1492 1492 1492 3916 7798 661 3571 2739
GoulburnHash 5415 9583 22218 26387 5655 5608 6237 6489 10949
AP 306 393 56190 394 403 29847 442 409 11048
Js 17934 18820 20734 21219 920 914 14195 1053 11974
Dek 0 0 0 0 153 0 290519 40 36339
ROT13 56171 56171 56190 56203 40601 29847 21522 1880 39823
x31 0 0 0 0 53710 174137 0 402996 78855
CrapWow 844 854 858 902 909 576574 874 421938 125469
djb 0 0 0 0 894 1714 0 1214544 152144
SuperFastHash 1302258 385692 384714 384720 154768 11499 298119 1149 365365
Sdbm 0 0 0 0 269853 636087 0 2389609 411944
Elf 0 1359899 1359899 1043099 1162284 2336423 142630 2672745 1259622
PJW-32 0 1359899 1359899 1043099 1162284 2336423 142630 2672745 1259622
Novak 2562948 2562948 2562948 2562948 2507396 2507342 2570635 292643 2266226
Fletcher 2603160 2431144 2431144 2431144 2574298 2574299 2592294 2717699 2544398
bp 2625299 2716199 2726289 2726298 2626289 2626299 2635299 2636289 2664783
Adler 2717856 2717856 2717856 2717856 2717856 2717856 2717856 2726054 2718881

Опять внеконкурсный тест. Тестовый набор - упорядоченные по возрастанию строки равной длины (128 символов), имеющие множество частичных совпадений. Набор символов - это латинский алфавит и цифры в десятичной системе исчисления. Такой тест не будет однозначно сложным, поскольку здесь не учитывается проблема появления коллизий при увеличении строки, от хэш-функции требуется лишь для разных текстов равной длины вычислить уникальное значение. Совпадения будут, но их будет меньше. Найти тестовые материалы можно здесь. Результаты в виде электронной таблицы доступны здесь.

Алгоритм Коллизии в тесте A Коллизии в тесте B Коллизии в тесте C Коллизии в тесте D Коллизии в тесте E Коллизии в тесте F Коллизии в тесте G Коллизии в тесте H Коллизии, среднее
MaHash8v64 0 0 0 0 0 0 0 0 0
MaHash3 842 840 789 907 883 879 826 871 855
MaHash4 881 824 835 885 898 846 818 865 857
lookup3 903 829 865 838 847 834 910 858 861
Murmur2AM 863 863 863 863 880 860 871 831 862
maPrime2d 860 860 860 860 823 872 898 878 864
MaHash5 846 872 867 857 876 881 864 865 866
MaHash1 811 907 869 803 873 839 909 918 866
faq6 878 878 878 878 846 842 870 884 869
OneAtTime 878 878 878 878 846 842 870 884 869
MaHash11 891 912 837 842 955 840 877 811 871
maHashMR1 862 850 846 869 858 898 911 874 871
MaHash2 843 930 840 855 881 912 849 867 872
crc32 884 884 884 884 884 884 866 872 880
MaHash8 894 899 923 859 884 918 860 862 887
ROT13 891 916 999 1014 871 844 876 899 914
maPrime2a 837 837 837 837 872 1753 892 902 971
maPrime2b 837 837 837 837 872 1753 892 902 971
SuperFastHash 942 1278 939 1231 1029 1010 933 980 1043
maPrime 858 858 858 858 838 3816 869 876 1229
maPrime2 864 864 864 864 848 3868 823 850 1231
RS 868 868 868 868 1673 3510 848 1696 1400
x17 879 879 879 879 1737 3413 859 1728 1407
DJB 868 868 868 868 1745 3505 820 1792 1417
BKDR 897 897 897 897 1750 3435 853 1827 1432
maBKDR 897 897 897 897 1750 3435 853 1827 1432
LY 950 950 950 950 1759 3478 868 1712 1452
Novak 826 826 826 826 1699 3426 848 3406 1585
MaHash7 843 1464 3281 3766 856 859 830 875 1597
MaHash6 862 1500 3312 3832 899 859 909 873 1631
JS 825 1456 3376 3938 845 877 887 874 1635
MaHash9 876 1464 3559 4029 857 879 854 886 1676
maPrime2c 895 878 891 839 872 7541 872 852 1705
MaHash 907 1645 4126 5024 842 910 899 915 1909
Murmur3 889 914 883 855 3983 7856 862 868 2139
FNV1 896 896 896 896 3808 7664 808 1713 2197
FNV1a 856 856 856 856 3919 7861 888 1729 2228
Crap8 817 855 891 804 4742 8561 816 863 2294
MaHash10 891 2417 6251 7609 897 876 916 890 2593
SBOX 1529 1598 1583 1542 4510 8370 1547 2219 2862
Murmur2a 864 864 864 864 7336 14819 853 881 3418
Murmur2 870 854 888 920 7555 14878 844 888 3462
FNV 934 934 934 934 7252 14889 836 3430 3768
x31 889 889 889 889 1748 3522 834 27516 4647
AP 898 898 898 898 27869 27869 904 882 7640
SDBM 824 824 824 824 1729 3478 842 54598 7993
Goulburn 5196 9334 22060 26281 5036 5080 5272 5169 10429
ELF 15334 209470 209470 210543 15894 15688 15313 15417 88391
PJW-32 15334 209470 209470 210543 15894 15688 15313 15417 88391
Fletcher 873 873 873 873 873 873 873 2660763 333359
Adler 155568 155568 155568 155568 155568 155568 155568 2722085 476383
CrapWow 876 834 868 833 2726297 2726297 892 857 682219
DEK 813 813 813 813 2726297 2726297 843 1845 682317
BP 35615 849097 2726283 2726298 35615 35615 17201 17201 805366

Опятьспецифичный тест. Но распространенный. Здесь мы проверяем как наш алгоритм будет хэшировать IP адреса (в виде строк). Конечно, IP адрес версии четыре - это 32-разрядное число и его можно хранить как есть и соответственно сортировать и производить поиск очень удобно. Но этот тест интересен тем, что строки все будут примерно одной длины, имеют много совпадений и набор символов ограничен числами 0-9 и точкой. Найти тестовые материалы можно здесь. Результаты в виде электронной таблицы доступны здесь.

Алгоритм Коллизии в тесте A Коллизии в тесте B Коллизии в тесте C Коллизии в тесте D Коллизии в тесте E Коллизии в тесте F Коллизии в тесте G Коллизии в тесте H Коллизии, среднее
AP 0 0 0 0 0 0 0 0 0
crc32 0 0 0 0 0 0 0 0 0
MaHash8v64 0 0 0 0 0 0 0 0 0
x17 0 0 0 0 0 0 0 420 53
BKDR 0 0 0 0 0 0 0 1284 161
maBKDR 0 0 0 0 0 0 0 1284 161
ROT13 0 0 60 117 1224 870 247 1431 494
maPrime 754 754 754 754 855 838 857 879 806
Murmur2A 893 798 798 868 891 829 795 881 844
Murmur2 880 800 784 898 849 891 824 850 847
Murmur2AM 847 854 854 762 920 882 807 856 848
maPrime2d 863 863 863 863 817 880 816 832 850
MaHash1 790 883 883 830 882 852 829 850 850
MaHash3 858 853 815 919 826 862 831 848 852
maPrime2c 865 837 926 833 887 845 751 869 852
MaHash4 863 844 883 847 853 827 902 843 858
MaHash8 830 887 911 902 843 829 828 844 859
maHashMR1 833 820 903 873 836 884 933 842 866
lookup3 885 845 895 867 881 866 827 876 868
MaHash11 928 873 914 843 827 873 829 865 869
MaHash5 823 911 828 862 888 881 884 883 870
maPrime2 883 883 883 883 867 868 887 824 872
MaHash2 872 872 832 866 917 861 920 872 877
faq6 890 890 890 890 865 868 913 875 885
OneAtTime 890 890 890 890 865 868 913 875 885
Murmur2M 1364 842 930 858 846 827 912 814 924
Murmur3 812 864 843 868 912 875 1363 868 926
maPrime2a 1369 1369 1369 1369 885 736 681 683 1058
maPrime2b 1369 1369 1369 1369 885 736 681 683 1058
FNV1 1080 1080 1080 1080 2227 4564 930 1734 1722
FNV1a 1108 1108 1108 1108 2333 4613 1073 1710 1770
MaHash6 1027 1444 3161 3879 911 944 2661 2588 2077
MaHash7 950 1462 3249 3893 960 995 2608 2662 2097
SBOX 1572 1491 1517 1487 2874 4831 1558 2265 2199
MaHash9 934 1581 3492 4253 941 1005 2694 2773 2209
FNV 736 736 736 736 3500 7166 810 3390 2226
MaHash 1087 1975 4339 4951 932 991 3277 3246 2600
JS 3620 4272 6278 6701 916 896 3045 2682 3551
MaHash10 1090 2609 6307 7603 1003 1016 5034 5108 3721
LY 5740 5740 5740 5740 5740 5740 0 0 4305
Crap8 1723 1693 1670 1653 833 823 68751 823 9746
Goulburn 5741 9987 22413 26734 5763 5547 17923 17707 13977
SuperFastHash 20533 26937 21965 31937 979 1641 55851 974 20102
DJB 0 0 0 0 0 1984 0 194327 24539
x31 0 0 0 0 3888 16776 6510 356070 47906
RS 0 0 0 0 0 612 824 600319 75219
CrapWow 39206 35814 35768 38757 16914 948 1254466 4347 178278
SDBM 0 0 0 0 38648 180053 1080 1598364 227268
DEK 253552 253552 253552 253552 253552 292720 548736 0 263652
Novak 451364 451364 451364 451364 451364 451364 431483 4196 392983
ELF 253884 1491098 1491098 1073498 1496649 2614453 1180553 2432140 1504172
PJW-32 253884 1491098 1491098 1073498 1496649 2614453 1180553 2432140 1504172
Fletcher 2647939 2489172 2489172 2489172 2693041 2693041 2489172 2720961 2588959
Adler 2721044 2721044 2721044 2721044 2721044 2721044 2721044 2726245 2721694
BP 2725238 2726192 2726289 2726298 2725238 2725238 2726294 2726294 2725885

Итоги и выводы

Итак, к чему мы пришли? Результат тестирования - это не просто набор таблиц и сухой статистики, которая мало кому интересна сама по себе.
Что нам показали эти цифры? Прежде всего то, что практически все популярные и широко используемые алгоритмы далеки от идеала. Практически все алгоритмы, за исключением crc32 (который хэшем не является), имеют некоторые конструктивные недостатки. Это особенно хорошо показали нам тесты групп E и F (где используются строки, состоящие из одинаковых блоков) и подобные. Именно всплески коллизий у популярных алгоритмов, которые вызваны особенностью их дизайна, позволили вырваться вперед моим алгоритмам - они пускай и не показывают эталонных результатов на простых текстах, но за счет большей устойчивости к столкновениям выдают хорошую статистику.

Из участников исследования - хэш-функций можно отметить алгоритмы lookup3 и faq6, которые показали себя очень достойно на всех тестах. Они не показали идеальных результатов, но и не давали всплесков, практически во всех экспериментах по усредненным значениям лишь немного отстали от победителей.

Автор алгоритмов семейства MurmurHash по результатам моего тестирования обратил внимание на неожиданные свойства своей функции. Недостаток, который выявил тест F заключается во всплеске коллизий функции MurmurHash2 на подобных наборах данных. Возможно, в скором времени автор предложит новый вариант своей функции, которая уже не содержит этот недостаток. В таком случае алгоритм (уже под новым именем) сможет значительно улучшить свои показатели.

Еще один момент - это выявленная перспективность (да и эффективность) использования таблиц замен. Табличная замена - очень быстрая операция. Их легко реализовать программно и аппаратно, а эффект использования такой функции позволяет пожертвовать незначительным снижением производительности. С помощью внедрения операций табличной подстановки был изменен дизайн некоторых алгоритмов и это дало плоды - число коллизий снизилось, были устранены всплески коллизий на проблемных тестах.

Ну, и использование счетчика блока как элемента вычисления хэш-функции. Оно оправдано, как показали некоторые тесты. Удалось решить некоторые проблемы функций BKDR и, что еще интереснее, MurmurHash.

Мнения специалистов

Посмотрим, что думают относительно моих функций независимые эксперты. Прежде всего, интересно взглянуть на тесты, который провел Peter Kankowski. Эти тесты оценивают функцию как со стороны производительности, так и со стороны числа коллизий на определенных наборах данных. По их результатам, MaHash8, MaHash4, MaHash11 имеют низкое число коллизий, но являются сравнительно медленными, поскольку выполняют большое количество операций на символ. MaPrime2c выглядит более интересно, что собственно тесты и показали.

Эксперт так же отметил, что MaHash8 медленнее, чем MaHash4, а она - медленнее, чем MaHash11, то есть улучшение качества хэширования достигается за счёт усложнения и замедления функции. MaHash тем не менее могут быть полезны, когда операция доступа к данным занимает больше времени, чем в тесте. Например, при использовании хэширования в файловой системе или базе данных.

Провести эти тесты самостоятельно вы можете, используя инструментарий тестирования, который размещен на сайте автора.

Продолжение следует?

Низкое число коллизий - это хорошо. Но что насчет скорости, насчет произвдительности наших супер - мега - ультра - крутых алгоритмов? Проверим и это. Не прошло и три года, как мы провели некоторые тесты производительности простых хеш-фунций общего назначения, не отказывыясь и от контроля их качества (в плане числа коллизий).

Используются технологии uCoz