繁体   English   中英

python中的快速、大宽度、非加密字符串散列

[英]fast, large-width, non-cryptographic string hashing in python

我需要在 python 中使用高性能字符串散列函数,该函数产生至少34位输出的整数(64 位有意义,但 32 位太少了)。 Stack Overflow 上还有其他几个类似的问题,但在我能找到的每个接受/赞成的答案中,都属于少数类别之一,这些类别不适用(出于给定的原因)。

  • 使用内置的hash()函数。 这个函数,至少在我正在开发的机器上(使用 python 2.7 和 64 位 cpu)产生一个适合 32 位的整数 - 对我的目的来说不够大。
  • 使用哈希库。 hashlib提供加密哈希程序,这是远远慢于他们需要的是对非加密的目的。 我觉得这是不言而喻的,但是如果您需要基准和引文来让您相信这一事实,那么我可以提供。
  • 使用string.__hash__()函数作为原型来编写您自己的函数。 我怀疑这将是正确的方法,除了这个特定函数的效率在于它使用 c_mul 函数,它包含 32 位 - 同样,对于我来说太小了! 非常令人沮丧,它是如此接近完美!

一个理想的解决方案应具有以下属性,其重要性相对松散。

  1. 输出范围至少扩展 34 位,可能是 64 位,同时在所有位上保持一致的雪崩特性。 (连接 32 位哈希往往会违反雪崩属性,至少在我的愚蠢示例中是这样。)
  2. 便携的。 在两台不同的机器上给定相同的输入字符串,我应该两次得到相同的结果。 这些值将存储在一个文件中以供以后重用。
  3. 高性能。 越快越好,因为在我正在运行的程序的执行过程中,这个函数将被调用大约 200 亿次(这是目前对性能至关重要的代码。)它不需要用 C 编写,它真的只需要优于 md5(在字符串的内置 hash() 领域中的某个地方)。
  4. 接受一个“扰动”(这里用什么词更好?)整数作为输入来修改输出。 我在下面放了一个例子(列表格式规则不允许我把它放在更近的地方。)我想这不是 100% 必要的,因为它可以通过手动扰动函数的输出来模拟,但是将它作为输入给了我好温暖的感觉。
  5. 完全用 Python 编写。 如果绝对,肯定需要用 C 编写,那么我想可以做到,但我会用 Python 编写的函数比用 C 编写的函数慢 20%,这只是由于使用两种不同语言的项目协调头痛. 是的,这是一个逃避,但这是一个愿望清单。

'Perturbed' 散列示例,其中散列值被一个小的整数值 n 剧烈改变

def perturb_hash(key,n):
    return hash((key,n))

最后,如果你对我到底在做什么感到好奇,我需要这样一个特定的哈希函数,我正在完全重写 pybloom 模块以显着提高其性能。 我成功了(它现在运行速度提高了大约 4 倍并使用了大约 50% 的空间),但我注意到有时如果过滤器变得足够大,它的假阳性率会突然飙升。 我意识到这是因为哈希函数没有处理足够的位。 32 位只能处理 40 亿位(请注意,过滤器处理的是位而不是字节),而我用于基因组数据的一些过滤器会加倍或更多(因此最少需要 34 位。)

谢谢!

看看MurmurHash3128 位变体 算法的页面包括一些性能数据。 应该可以将其移植到 Python,纯的或作为 C 扩展。 更新了作者建议使用 128 位变体并丢弃您不需要的位)。

如果 MurmurHash2 64 位适合您, pyfasthash 包中有一个 Python 实现(C 扩展),其中包括一些其他非加密哈希变体,尽管其中一些仅提供 32 位输出。

更新我为 Murmur3 哈希函数做了一个快速的 Python 包装。 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

使用内置的 hash() 函数。 这个函数,至少在我正在开发的机器上(使用 python 2.7 和 64 位 cpu)产生一个适合 32 位的整数 - 对我的目的来说不够大。

这不是真的。 内置哈希函数将在 64 位系统上生成 64 位哈希。

这是来自Objects/stringobject.c (Python 版本 2.7)的 python str 哈希函数:

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;
}

小心内置哈希函数!

从 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

“字符串”:我假设您希望散列 Python 2.x str对象和/或 Python3.x bytes和/或bytearray对象。

这可能会违反您的第一个约束,但是:考虑使用类似

(zlib.adler32(strg, perturber) << N) ^ hash(strg)

得到一个 (32+N) 位的哈希值。

如果您可以使用 Python 3.2,则 64 位 Windows 上的哈希结果现在是 64 位值。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM