为什么我的ELF哈希实现具有如此高的冲突率

时间:2012-02-06 12:07:16

标签: c# .net hash

我目前正在努力选择一些用于Object.GetHashCode()覆盖的通用散列函数。最初,根据this site的建议,我开始使用ELF。我的C#实现如下:

public int Generate(byte[] key) {
    const uint c = 0xf0000000;

    uint h = 0,
         g = 0;

    unchecked {
        for (int i = 0, len = key.Length; i < len; i++) {
            h = (h << 4) + key[i];

            if ((g = h & c) != 0)
                h ^= g >> 24;

            h &= ~g;
        }
    }

    return (int)h;
}

我的测试用例由524,288个唯一值组成,分为短(1-64)和长(256-2048)字符串(有限的ASCII字符集)和任意二进制数据(每个131,072),以便在各种情况下测试每种算法

我也理解这个测试场景的局限性。散列算法在散列(例如URL)时可能表现异常,但在散列JPG或其他东西时可能会很糟糕。在我看来,随机字符串/二进制是选择通用函数的最佳起点。我很高兴听到为什么不是这种情况的理由。

我执行了3次单独的测试运行(每次生成一组新的随机字符串/字节)并对结果取平均值。

与我正在测试的其他算法相比,ELF算法产生了可怕数量的碰撞:

  • 短串:817次碰撞(约0.5%失败率)。
  • 短二进制:550次碰撞(~0.4%失败率)
  • 长串/二进制:34次碰撞(~0.025%失败率)。

为了将其置于上下文中,我测试的其他3种算法平均在相同测试中平均产生3-10次碰撞。它也是4中最慢的,所以在这一点上它似乎完全没用。

完整结果:

           Strings     Binary
Algorithm  short:long  short:long
ELF        817:40      550:28
FNV        1.6:2       0.6:2.6
OAT        9:9.6       14:5
Jenkins*   2:1.3       12:3.6

* A close approximation of the lookup3 hash function.

因此,对于ELF正在努力的相同随机样本(我已经生成了3个独立的集合),所有其他经过测试的算法都会产生更少的冲突。

我搜索了ELF算法的变体,但我发现的几个例子似乎与我实现的一致。我见过的唯一变化就是这个问题:Using ELF to produce a tweaked hashmap。此变化包括if块中的h &= g >> 24,并将结果剪辑为31位。我测试了这种变化,它产生了同样可怕的结果。

我做了一些微妙但可怕的错误吗?我无法理解为什么它的表现如此糟糕,因为据称它在Unix中被广泛使用。

3 个答案:

答案 0 :(得分:10)

加密哈希,它是一个哈希表哈希。

对于打算在哈希表中使用的哈希函数,这是一个非常合理的性能。通常,您将存储数百到数十万个对象,并希望快速存储和检索对象。

你可以通过划分为桶来实现这一点,每个桶都包含一个链表(或者一个数组)。然后计算哈希值,并在除以桶的数量时取余数,找到桶。然后,您将遍历链接列表,比较每个对象以找到您想要的对象。

如果存储桶为空,则找不到该对象。然后,您可以根据应用程序创建一个或采取其他适当的操作。

哈希表的大小应该与预期存储的项目数量(或更多)具有大致相同的存储桶数量,因此大多数搜索都会找到一个包含零个,一个或两个条目的存储桶。

对于性能,您希望平衡计算哈希的费用与在遇到冲突时遍历非常短的链表的费用。考虑到这一点,设计了ELF和类似用途的哈希函数的实现。

简而言之:

  • 在哈希表中,偶尔的冲突是值得为更快的哈希付出的代价。
  • 在加密哈希中,慢哈希值是为避免冲突而付出的代价。

如果碰撞在您的应用程序中存在问题,请使用SHA1或SHA256或设计时考虑到这一点。

注意:为了用作object.GetHashCode()的实现,哈希码仅用于加速比较(“快速失败”)和用于哈希表。你不需要它完全抵抗,因为如果你碰撞,你将回到完全相等的比较。您需要 均衡的效果 。我建议只扫描最重要的字段(使用自己的GetHashCode())并对值进行异或。

修改:请在此处查看这些哈希值:

答案 1 :(得分:10)

32位散列中524000个随机样本中预期的冲突数为34。

你得到34个长字符串冲突,所以对于长字符串,这个算​​法的表现或多或少与预期一致。

哈希冲突在短字符串上的可能性要大得多,因为数据中的熵少得多,所以对于我来说,在小字符串上获得的性能要差一些,这绝对不足为奇。

令人惊讶的是,您只与其他哈希算法发生了10次冲突。我本以期待更多。

关于原始速度表现的主题:你可能会更好地停止这么聪明。抖动可以识别并优化极其常见的模式:

for(int i = 0; i < array.Length; ++i)
    do something with array[i]

以避免重新计算长度并避免对阵列访问进行范围检查。通过尝试变得聪明并避免重新计算长度,您可能会将抖动弄成不再优化距离检查。

如果您希望始终避免范围检查,您可以随时转到不安全的代码;将数组固定到位,获取指向它的指针,然后递增指针,就像你在C中编写程序一样。你负责确保那时的内存安全,但是几率很高它会更快。 / p>

当然,“扶手椅”性能分析正是您所付出的代价;要获得真正的分析,尝试,看看会发生什么。

答案 2 :(得分:0)

actual ELF implementation返回unsigned long,来源内部使用unsigned long。我不能肯定地说,但我的直觉是你的实现只是通过处理int而丢掉了太多有趣的东西。