有效地将 16 位整数哈希为 256 位空间

Hash 16-bit integer to a 256-bit space efficiently

本文关键字:空间 哈希 整数 有效地      更新时间:2023-10-16

听起来很奇怪,但这就是我想做的。 我想获取 16 位整数的整个序列,并以统一映射到 256 位空间的方式对每个整数进行哈希处理。

这样做的原因是我正在尝试将 16 位数字空间的子集放入 256 位布隆过滤器中,以便快速进行成员资格测试。

我可以在每个整数上使用一些众所周知的哈希函数,但我正在寻找一个非常有效的实现(只有几条指令),以便在 GPU 着色器程序中运行良好。 我觉得已知哈希输入只有 16 位的事实可以通知哈希函数以某种方式设计,但我没有看到解决方案。

有什么想法吗?


编辑

根据回答,我最初的问题令人困惑。 对此感到抱歉。 我将尝试用一个更具体的例子来重申它:

我有一个来自集合 S 的 n 个数字的子集 S1,它在 (0, 2^16-1) 范围内。 我需要用一个由单个哈希函数构造的 256 位布隆过滤器来表示这个子集 S1。 使用布隆过滤器的原因是空间考虑。 我选择了 256 位布隆过滤器,因为它符合我的空间要求,并且误报的可能性足够低。 我正在寻找一个非常简单的哈希函数,它可以从集合 S 中获取一个数字并以 256 位表示它,这样每个位的概率大致相等,为 1 或 0。

哈希函数要求简单性的原因是,这个哈希函数必须每个像素运行数千次,所以任何我可以修剪指令的地方都是胜利。

如果你将 16 位值乘以(使用uint32_t)素数(或任何奇数),p2^31 和 2^32 之间,那么你"可能"在 32 位空间中相当均匀地涂抹结果。然后你可能想添加另一个素数值,以防止0映射到0(你希望每个位具有相等的概率01,2^256 中只有一个输入值应该输出全零,并且由于只有 2^16 个输入,这意味着你不希望它们中的任何一个输出全部为零)。

所以这就是如何通过一个操作将 16 位扩展到 32 位(加上加载常量所需的任何指令)。使用四种不同的值p1...p4得到 256 位,并使用不同的p值运行一些测试以找到好的测试(即那些产生的误报不会比您预期的 Bloom 过滤器多的误报,考虑到您正在编码的集合的大小并假设一个理想的哈希函数)。例如,我很确定-1是一个糟糕的p值。

但是,无论值有多好,您都会看到一些相关性:例如,正如我上面描述的那样,所有 4 个独立值中的最低位将是相等的,这是一个非常严重的依赖关系。因此,您可能还需要更多"混合"操作。例如,你可能会说最终输出的每个字节应该是我所描述的两个字节的异或(而不是两个最小签名字节!),只是为了摆脱简单的算术关系。

不过,除非我误解了这个问题,否则这不是布隆过滤器通常的工作方式。通常,您希望哈希为每个输入生成精确固定数量的设置位,并且计算误报率的所有算术都依赖于此。这就是为什么对于大小为 256 位的 Bloom 过滤器,您通常会k8 位哈希,而不是一个 256 位哈希。k通常小于滤波器大小的一半(以位为单位)(最佳值是滤波器中每个值的位数乘以ln(2)约为0.7)。所以通常你不希望每个比特为 1 的概率高达 0.5。

原因是,一旦您将 4 个这样的 256 位值放在一起,过滤器中的几乎所有位都被设置了(其中 16 个中有 15 个)。所以你已经看到很多误报了。

但是,如果你已经完成了数学计算,并且你对单个哈希函数产生可变数量的设置位感到满意,平均其中的一半,那么就足够公平了。或者数字 256 的重复出现只是一个巧合,因为k恰好是您选择的设置大小的 32,而您实际上使用 256 位哈希作为 32 个 8 位哈希?

[编辑:您的评论澄清了这一点,但无论如何k不应该太高以至于您总共需要 256 位哈希。显然,在这种情况下使用每个值超过 16 位(即少于 16 个值)的布隆过滤器是没有意义的,因为使用相同的空间量,您可以只列出值,误报率为 0。每个值 16 位的筛选器给出的误报率约为 1/2200。即使在那里,最优k也只有 23,也就是说,您应该在过滤器中为集合中的每个值设置 23 位。如果你希望集合大于 16 个值,那么你需要为每个元素设置更少的位数,你会得到更高的误报率。

我相信提出的问题有些混乱。我将首先尝试清除我在上面注意到的任何不一致之处。

OP最初表示,他正试图将较小的空间映射到较大的空间。如果确实如此,则无需使用布隆过滤器算法。相反,正如上面的评论中所建议的那样,身份函数是设置和测试每个位所需的唯一"哈希"函数。但是,我断言这不是OP真正想要的。如果是这样,那么OP必须在内存中存储2^256位(基于问题的陈述方式),以便16位整数的空间(即2^16) 小于他的设定大小;这是不合理的内存量,极不可能是这种情况。

因此,我假设问题约束如下:我们有一个 256 位位向量,我们希望在其中映射 16 位整数的空间。也就是说,我们有 256 位可用于映射2^16可能的不同整数。因此,我们实际上并没有映射到一个更大的空间,而是映射到一个更小的空间。类似地,它确实看起来(再次,如前面的评论中指出的那样)OP 正在请求单个哈希函数。如果是这种情况,那么对布隆过滤器的工作原理存在明显的误解。

布隆过滤器通常使用一组与哈希无关的哈希函数来减少误报。在不赘述太多细节的情况下,布隆过滤器的每个输入都运行所有n哈希函数,然后针对每个函数测试位向量中的结果索引。如果测试的所有索引都设置为1,则该值可能在集合中(在所有n哈希函数中发生适当的冲突或重叠,将发生误报)。此外,如果任何索引设置为0,则该值绝对不在集合中。考虑到这一点,重要的是要注意完全饱和的泛光过滤器没有任何好处。也就是说,对布隆过滤器的每个查询都将返回该项目在集合中。

哈希函数问题

现在,回到OP最初的问题。最好使用已知的哈希算法(因为这些算法在数学上很难编写,而且"滚动你自己的"通常不会有好结果)。如果您担心低至时钟周期的效率,请使用适合您的架构的汇编语言自行实现算法,以减少每个哈希函数的运行时间。请记住,从算法上讲,哈希函数应该在O(1)时间内运行,因此如果正确实现,它们应该不会产生太多开销。首先,我建议您考虑修改后的伯恩斯坦哈希。我在下面为您的具体情况编写了一个版本(主要用于示例目的):

unsigned char modified_bernstein(short key)
{
unsigned ret = key & 0xff;
ret = 33 *  ret ^ (key >> 8);
return ret % 256; // Try to do some modulo math to keep it in range
}

我采用的伯恩斯坦方法通常作为输入字节数的函数运行。由于short类型是2字节或16-bits,我已经从算法中删除了所有变量和循环,并简单地执行了一些位摆动来获取每个字节。最后,unsigned char可以返回[0,256)范围内的值,这会强制哈希函数在位向量中返回有效索引。