【问题标题】:Hash function for 64 bit to 10 bits64 位到 10 位的散列函数
【发布时间】:2012-07-06 09:25:20
【问题描述】:

我想要一个哈希函数,它需要一个长数字(64 位)并产生 10 位的结果。用于此目的的最佳哈希函数是什么。输入基本上是变量的地址(在 Linux 上地址是 64 位或 8 字节),所以我的哈希函数应该为此目的进行优化。

【问题讨论】:

  • 您能给我们提供关于您的宇宙中 64 位值分布的哪些信息?
  • 没有适用于所有情况的“最佳”哈希函数。您必须研究输入数字的分布和特征。
  • 输入是Linux上变量的地址。
  • @MetallicPriest:在这种情况下,您可以删除低 4+ 位(假设所有内容都对齐),并且 atm 地址空间限制为 47 位,因此这意味着您只需要 43 位哈希(如果你愿意对未来不太安全)
  • 虽然当然 它没有伤害 散列所有位,包括具有低熵(位 0-2)或始终相同(最高 16 位)的位在用户空间中,至少目前,始终为零)。这样,从现在起的 3-4 年内,你就可以避免重用这个函数的愚蠢错误,忘记你最初设计它的原因和前提,将它用于不符合假设的事情。这可以避免公开羞辱,例如一个星期的时间被浪费了,因为哈希表表现不佳,没有人能解释原因。

标签: c linux gcc hash x86-64


【解决方案1】:

我会这样说:

uint32_t hash(uint64_t x)
{
    x >>= 3;
    return (x ^ (x>>10) ^ (x>>20)) & 0x3FF;
}

最不重要的 3 位不是很有用,因为大多数变量都是 4 字节或 8 字节对齐的,所以我们将它们删除。 然后我们取出接下来的 30 位,并将它们混合在一起(XOR),每块 10 位。

当然,您也可以使用(x>>30)^(x>>40)^(x>>50),但我不确定它们在实践中是否会有所作为。

【讨论】:

  • 由于您使用 xor-shift 进行混合,我建议使用已知的 275 个三元组之一,其 64x64 矩阵中的周期为 2^64-1,如 Marsaglia 所述,例如 (7,11 ,10) 或 (21,17,48)。由于这以伪随机方式混合位,没有已知的奇怪之处,因此在执行 &0x3ff 之前将所有单词异或在一起是有效的。这样,每个输入位都应该有机会影响所有输出位。也许不像加密哈希那样完美地 50:50 分布,但尽可能好。除此之外,仍然是一个好主意,+1
【解决方案2】:

我编写了一个玩具程序查看堆栈、数据区和堆上的一些真实地址。基本上我声明了 4 个全局变量、4 个局部变量并做了 2 个mallocs。我在打印地址时丢掉了最后两位。这是其中一次运行的一个输出:

 20125e8
 20125e6
 20125e7
 20125e4
3fef2131
3fef2130
3fef212f
3fef212c
 25e4802
 25e4806

这告诉我什么:

  1. 此输出中的 LSB(地址的第 3 位)经常“打开”“关闭”。所以在计算哈希时我不会放弃它。减少 2 个 LSB 似乎就足够了。
  2. 我们还看到低 8-10 位的熵更多。在计算哈希时,我们必须使用
  3. 我们知道在 64 位机器上,virtual addresses are never more than 48 bits wide

接下来我会做什么

/* Drop two LSBs.  */
a >>= 2;

/* Get rid of the MSBs. Keep 46 bits. */
a &= 0x3fffffffffff;

/* Get the 14 MSBs and fold them in to get a 32 bit integer.
The MSBs are mostly 0s anyway, so we don't lose much entropy.  */
msbs = (a >> 32) << 18;
a ^= msbs;

现在我们通过 decent 'half avalanche' hash function 传递它,而不是滚动我们自己的。 'Half avalanche' 意味着输入的每个位都有机会影响相同位置的位并且更高

uint32_t half_avalanche( uint32_t a)
{
    a = (a+0x479ab41d) + (a<<8);
    a = (a^0xe4aa10ce) ^ (a>>5);
    a = (a+0x9942f0a6) - (a<<14);
    a = (a^0x5aedd67d) ^ (a>>3);
    a = (a+0x17bea992) + (a<<7);
    return a;
}

对于 10 位哈希,使用返回的 uint32_t 的 10 个 MSB。如果您为N 位哈希选择N MSB,哈希函数继续正常工作,每增加一个位,桶数就会有效地翻倍。

我有点无聊,所以我为此写了一个玩具基准测试。没什么特别的,它在堆上分配了一堆内存并尝试了我的哈希如上所述。来源可以来自 here。示例结果:

1024 个桶,生成 256 个值,29 次碰撞
1024 个桶,生成 512 个值,103 次碰撞
1024 个桶,生成 1024 个值,370 次碰撞

下一步:我尝试了这里回答的其他两个哈希值。他们都有相似的表现。看起来像:只选择最快的;)

【讨论】:

    【解决方案3】:

    对于大多数发行版来说,最好的方法是按素数模数,1021 是最大的 10 位素数。无需去除低位。

    static inline int hashaddress(void *v)
    {
            return (uintptr_t)v % 1021;
    }
    

    如果您认为性能可能是一个问题,请准备一些替代品并在您的实际程序中与他们比赛。微基准是浪费;几个周期的差异几乎肯定会被缓存效应淹没,而且大小很重要。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-07-13
      • 1970-01-01
      • 1970-01-01
      • 2013-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多