Быстрое, некриптографическое хеширование строк в python
У меня есть потребность в высокопроизводительной функции хеширования строк в Python, которая производит целые числа, по крайней мере, с 34 битами вывода (64 бита имеет смысл, но 32- это слишком мало). Есть несколько других вопросов, таких как этот, о переполнении стека, но из тех, которые я получил, каждый найденный / одобренный ответ попал в одну из нескольких категорий, которые неприменимы (по указанной причине).
- Используйте встроенный
hash()
функция. Эта функция, по крайней мере, на машине, для которой я разрабатываю (с Python 2.7 и 64-битным процессором), выдает целое число, которое умещается в пределах 32 бит - недостаточно для моих целей. - Используйте hashlib. hashlib предоставляет криптографические процедуры хеширования, которые намного медленнее, чем они должны быть для не криптографических целей. Я нахожу это само собой разумеющимся, но если вам нужны критерии и цитаты, чтобы убедить вас в этом факте, то я могу это предоставить.
- Использовать
string.__hash__()
функционировать как прототип, чтобы написать свою собственную функцию. Я подозреваю, что это будет правильный путь, за исключением того, что эффективность этой конкретной функции заключается в использовании функции c_mul, которая оборачивается около 32 бит - опять же, слишком мало для моего использования! Очень расстраивает, это так близко к идеалу!
Идеальное решение будет иметь следующие свойства в относительном, свободном порядке важности.
- Имеют выходной диапазон, расширяющий по крайней мере 34 бита, вероятно 64 бита, сохраняя при этом согласованные лавинные свойства для всех битов. (Конкатенация 32-битных хэшей имеет тенденцию нарушать свойства лавины, по крайней мере, с моими тупыми примерами.)
- Портативный. Учитывая одну и ту же строку ввода на двух разных машинах, я должен получить один и тот же результат оба раза. Эти значения будут сохранены в файле для последующего повторного использования.
- Высокая производительность. Чем быстрее, тем лучше, так как эта функция будет вызываться примерно 20 миллиардов раз во время выполнения программы, которую я запускаю (в данный момент это критичный для производительности код). Ее не нужно писать на C, на самом деле просто нужно превзойти md5 (где-то в области встроенного хеша () для строк).
- Примите целое число "возмущение" (какое слово лучше использовать здесь) в качестве входных данных для изменения выходных данных. Я привел пример ниже (правила форматирования списка не позволили бы мне разместить его ближе.) Я полагаю, что это не на 100% необходимо, поскольку его можно смоделировать, возмущая вывод функции вручную, но наличие его в качестве входных данных дает мне приятное теплое чувство.
- Написано полностью на Python. Если это абсолютно положительно должно быть написано на C, тогда я думаю, что это можно сделать, но я бы взял на 20% более медленную функцию, написанную на python, по сравнению с более быстрой в C, просто из-за головной боли при координации проекта при использовании двух разных языков., Да, это отговорка, но это список пожеланий здесь.
Пример возмущенного хэша, где значение хеша радикально изменяется маленьким целочисленным значением n
def perturb_hash(key,n):
return hash((key,n))
Наконец, если вам интересно, что, черт возьми, я делаю, что мне нужна такая специфическая хеш-функция, я делаю полную переписывание модуля pybloom, чтобы значительно повысить его производительность. Мне это удалось (теперь он работает примерно в 4 раза быстрее и занимает около 50% пространства), но я заметил, что иногда, если фильтр становится достаточно большим, он внезапно дает ложноположительные результаты. Я понял, что это потому, что хэш-функция не адресовала достаточно битов. 32 бита могут адресовать только 4 миллиарда бит (учтите, что фильтр адресует биты, а не байты), а некоторые из фильтров, которые я использую для геномных данных, удваивают это или более (следовательно, минимум 34 бита).
Спасибо!
6 ответов
Взгляните на 128-битный вариант MurmurHash3. Страница алгоритма содержит некоторые показатели производительности. Должно быть возможно перенести это на Python, чистый или как расширение C. (Обновлен автор рекомендует использовать 128-битный вариант и выбрасывать ненужные вам биты).
Если MurmurHash2 работает для вас 64- битно, в пакете pyfasthash есть реализация Python (расширение C), которая включает в себя несколько других не криптографических хэш-вариантов, хотя некоторые из них предлагают только 32-битный вывод.
Обновление Я сделал быструю оболочку Python для хэш-функции Murmur3. Проект Github уже здесь, и вы также можете найти его в Python Package Index; ему просто нужен компилятор C++ для сборки; Повышение не требуется.
Пример использования и сравнение сроков:
import murmur3
import timeit
# without seed
print murmur3.murmur3_x86_64('samplebias')
# with seed value
print murmur3.murmur3_x86_64('samplebias', 123)
# timing comparison with str __hash__
t = timeit.Timer("murmur3.murmur3_x86_64('hello')", "import murmur3")
print 'murmur3:', t.timeit()
t = timeit.Timer("str.__hash__('hello')")
print 'str.__hash__:', t.timeit()
Выход:
15662901497824584782
7997834649920664675
murmur3: 0.264422178268
str.__hash__: 0.219163894653
БУДЬТЕ ОСТОРОЖНЫ СО ВСТРОЕННОЙ ФУНКЦИЕЙ ХЭША!
Начиная с Python3, ему каждый раз при запуске интерпретатора загружается другое семя (я не знаю более подробной информации), поэтому он каждый раз генерирует разные значения - но не с собственными числовыми типами.
$ python3 -c 'print(hash("Hello!"), hash(3.14))'
-1756730906053498061 322818021289917443
$ python3 -c 'print(hash("Hello!"), hash(3.14))'
-4556027264747844925 322818021289917443
$ python3 -c 'print(hash("Hello!"), hash(3.14))'
-4403217265550417031 322818021289917443
Взгляните на xxHash, там также есть пакет pip .
xxHash - это чрезвычайно быстрый алгоритм хеширования, работающий с ограничениями по скорости ОЗУ. Он успешно завершает набор тестов SMHasher, который оценивает коллизию, дисперсию и случайность хэш-функций. Код легко переносится, а хэши идентичны на всех платформах (прямой / большой порядок байтов).
Я давно использую xxHash (мой типичный вариант использования - хэширование строк, а не в целях безопасности), и я действительно доволен производительностью.
Используйте встроенную функцию hash(). Эта функция, по крайней мере, на машине, для которой я разрабатываю (с Python 2.7 и 64-битным процессором), выдает целое число, которое умещается в пределах 32 бит - недостаточно для моих целей.
Это не правда. Встроенная хеш-функция генерирует 64-битный хеш в 64-битной системе.
Это хеш-функция Python str из Objects/stringobject.c
(Python версия 2.7):
static long
string_hash(PyStringObject *a)
{
register Py_ssize_t len;
register unsigned char *p;
register long x; /* Notice the 64-bit hash, at least on a 64-bit system */
if (a->ob_shash != -1)
return a->ob_shash;
len = Py_SIZE(a);
p = (unsigned char *) a->ob_sval;
x = *p << 7;
while (--len >= 0)
x = (1000003*x) ^ *p++;
x ^= Py_SIZE(a);
if (x == -1)
x = -2;
a->ob_shash = x;
return x;
}
"strings": я предполагаю, что вы хотите хэшировать Python 2.x str
объекты и / или Python3.x bytes
и / или bytearray
объекты.
Это может нарушить ваше первое ограничение, но: подумайте об использовании чего-то вроде
(zlib.adler32(strg, perturber) << N) ^ hash(strg)
чтобы получить (32+N)-битовый хеш.
Если вы можете использовать Python 3.2, результат хеширования в 64-битной Windows теперь будет 64-битным значением.