Какой лучший алгоритм хэширования использовать для строки stl при использовании hash_map?

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

Ответы

Ответ 1

Я работал с Paul Larson из Microsoft Research по некоторым реализациям хэш-таблицы. Он исследовал ряд функций хеширования строк на множестве наборов данных и обнаружил, что простое умножение на 101 и цикл добавления работает на удивление хорошо.

unsigned int
hash(
    const char* s,
    unsigned int seed = 0)
{
    unsigned int hash = seed;
    while (*s)
    {
        hash = hash * 101  +  *s++;
    }
    return hash;
}

Ответ 2

Из моего старого кода:

/* magic numbers from http://www.isthe.com/chongo/tech/comp/fnv/ */
static const size_t InitialFNV = 2166136261U;
static const size_t FNVMultiple = 16777619;

/* Fowler / Noll / Vo (FNV) Hash */
size_t myhash(const string &s)
{
    size_t hash = InitialFNV;
    for(size_t i = 0; i < s.length(); i++)
    {
        hash = hash ^ (s[i]);       /* xor  the low 8 bits */
        hash = hash * FNVMultiple;  /* multiply by the magic number */
    }
    return hash;
}

Быстро. На самом деле очень быстро.

Ответ 3

Boost имеет библиотеку boost:: hash, которая может предоставлять некоторые основные хеш-функции для большинства распространенных типов.

Ответ 4

Это всегда зависит от вашего набора данных.

Я для одного имел удивительно хорошие результаты, используя CRC32 строки. Работает очень хорошо с широким спектром различных наборов ввода.

Множество хороших реализаций CRC32 легко найти в сети.

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

http://smallcode.weblogs.us/ < - далее вниз по странице.

Ответ 5

Я использую хэш Jenkins для записи библиотеки фильтров Bloom, у нее отличная производительность.

Подробности и код можно найти здесь: http://burtleburtle.net/bob/c/lookup3.c

Это то, что Perl использует для своей операции хэширования, fwiw.

Ответ 6

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

Если вы не хешируете фиксированный набор слов, то, очевидно, это не применяется.

Ответ 7

Одно классическое предложение для хеш-строки состоит в том, чтобы переходить через буквы один за другим, добавляя их значения ascii/unicode в аккумулятор, каждый раз умножая аккумулятор на простое число. (разрешая переполнение хэш-значения)

  template <> struct myhash{};

  template <> struct myhash<string>
    {
    size_t operator()(string &to_hash) const
      {
      const char * in = to_hash.c_str();
      size_t out=0;
      while(NULL != *in)
        {
        out*= 53; //just a prime number
        out+= *in;
        ++in;
        }
      return out;
      }
    };

  hash_map<string, int, myhash<string> > my_hash_map;

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

Вы можете попытаться кэшировать хэш-значение лучше, создав новый подкласс basic_string, который помнит свое значение хэша, если значение слишком часто вычисляется. Однако hash_map должен делать это внутренне.

Ответ 8

Я немного искал, и забавно, что появился маленький алгоритм Пола Ларсона http://www.strchr.com/hash_functions как имеющие наименьшие столкновения любого тестируемого в ряде условий, и это очень быстро для одного, которое оно развернуто или управляется таблицей.

Larson является простым умножением на 101 и добавляет цикл выше.

Ответ 9

Python 3.4 включает новый алгоритм хеширования, основанный на SipHash. PEP 456 очень информативен.

Ответ 10

Если ваши строки в среднем длиннее одной строки кэша, но их длина + префикс достаточно уникальны, подумайте о том, чтобы иметь длину + первые 8/16 символов. (Длина содержится в самом объекте std::string и поэтому дешева для чтения)

Ответ 11

От Хеш-функции до конца:

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

Его прекрасный выбор, но давайте посмотрим позже, если мы можем в целом добиться большего. Еще один прекрасный выбор, особенно если вы знаете больше о своих данных, чем "его будет неизвестное количество байтов", это сворачивать ваши собственные (например, см. Ответы Won Chuns или Runes modified xxHash/Murmur, которые специализированы для 4-байтных ключей и т.д.). Если вы знаете свои данные, всегда старайтесь выяснить, можно ли использовать эти знания для хорошего эффекта!

Без дополнительной информации я бы рекомендовал MurmurHash в качестве общего назначения некриптографическая хэш-функция. Для небольших строк (размера среднего идентификатора в программах) очень простой и известный djb2 и FNV очень хороши.

Здесь (размеры данных < 10 байт) мы видим, что интеллектуальность ILP других алгоритмов не проявляет себя, и супер-простота FNV или djb2 выигрывает в производительности.

djb2

unsigned long
hash(unsigned char *str)
{
    unsigned long hash = 5381;
    int c;

    while (c = *str++)
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */

    return hash;
}

FNV-1

hash = FNV_offset_basis
for each byte_of_data to be hashed
     hash = hash × FNV_prime
     hash = hash XOR byte_of_data
return hash

FNV-1A

hash = FNV_offset_basis
for each byte_of_data to be hashed
     hash = hash XOR byte_of_data
     hash = hash × FNV_prime
return hash

Заметка о безопасности и доступности

Функции хэширования могут сделать ваш код уязвимым для атак типа "отказ в обслуживании". Если злоумышленник может заставить ваш сервер обрабатывать слишком много коллизий, ваш сервер может не справиться с запросами.

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