冲突解决:二次探测与单独链接
Posted
技术标签:
【中文标题】冲突解决:二次探测与单独链接【英文标题】:Collision Resolution : Quadratic Probing vs. Separate Chaining 【发布时间】:2010-12-22 06:47:33 【问题描述】:好的,所以我一直在对哈希表和不同的冲突解决问题进行一些实验。我试图找出哪个更有效地进行查找,一个使用单独链接或二次探测来解决冲突的哈希表。我的结果表明,即使对于 0.4 或 0.2 这样的小负载因子,单独的链接也比二次探测更快。是这种情况还是我的结果有误?
【问题讨论】:
【参考方案1】:两种方法在处理成本上的区别在于 (带链接) - 间接,即指针解引用 对比 (使用二次探测) - 评估 [简单但复合的] 算术公式 - 索引到新位置 - 可能重复(由于探测值和存储在这些位置的非目标值之间的冲突;不必担心链接。
因此链接速度更快应该不足为奇;指针解引用是大多数 CPU 的“本机”指令,与索引到数组的指令相当(在大多数情况下相同),将算术运算和可能的冲突作为不利于探测的开销。最简单的探测序列公式将需要一些 CPU 指令(初始化 stepNr,通常是 stepNr 的一些移位,添加到当前位置/探针),其本身很容易比指针解引用慢几倍。 (Poss.caveat: please see 'Edit' 随后不久,因为它讨论了链接如何导致更多的 CPU 级缓存未命中,从而使其效率低于线性探测)
二次(或其他形式)链接的优点是
更简单的存储管理逻辑(无动态分配) 更快的插入(因为存储更简单) 总体上降低了存储要求考虑这个空间与速度(或插入时间与搜索时间)妥协在非常广泛的术语中,链接的存储开销(主要用于指针本身,不考虑可能堆管理开销)用于存储预先计算的[探测会发生什么]“探测位置”的值。由于这些计算很容易完成,因此链接方法在搜索时更快。编辑(感谢 Ants Aasma) 这个论点 [关于预先计算的位置] 的一个警告是,在现代 CPU 及其缓存上,运行小计算的成本可能远低于缓存未命中时访问 [数据] 内存的成本。这表明,按顺序进行探测(或更一般地,使用产生物理上接近碰撞点的位置的探测函数)可能优于链接策略,因为缓存未命中率较低。从这个角度来看,纯顺序探测方法是探测功能中最好的,因为它的计算非常简单,但更重要的是因为它最大限度地提高了缓存命中的几率。考虑到这一点,当散列函数具有良好的分布并且负载因子很小(因此在初始碰撞后具有短/局部搜索路径)应该尝试线性(或非常局部)探测方法;但是,应该避免探测提供非本地搜索路径的函数。
很难具体评论问题中提到的实验,例如不知道哈希的大小(如果这个大小与 CPU 中的字/寄存器的大小相匹配,则算法可以更快),或者不知道碰撞率(让我们假设一个好的、分布良好的散列函数)。 随着您不断对此进行试验,收集一组单独的时间/统计数据来访问带有哈希槽的项目与产生冲突的项目会很有趣。
“...even for small load factors...”中的“even”表示您期望链接的相对优势应随着负载进一步增加,因此随着碰撞变得越来越多。我也希望会是这样。 此外,增加负载可能说明探测的另一个缺点:探测循环和/或更一般地没有空间容纳特定项目的情况。
【讨论】:
在今天的硬件计算 CPU 周期不会产生任何有意义的结果。相关数字是高速缓存未命中的数量,未命中高速缓存的单个内存访问很容易花费几百个周期。这表明,如果哈希函数分布良好且负载因子合理,则可以通过顺序探测获得更好的性能。 @John:我建议您也使用a purely linear probing function
进行实验。如前所述,重要的是要密切注意 [hash] 碰撞率,因为当负载因子处于中等水平时,探测方法可能处于最佳状态:在低负载下,碰撞太少,无法将链接置于显着位置缺点,并且在负载过高的情况下,[探测] 冲突的数量开始损害相对于链接的探测方法。
+1 这值得一票以上,只是因为我不明白
@Robert G,大声笑...也许我会坚持下去,即迟钝的答案和通常花哨但有缺陷的曝光,以便人们支持我 ;-) 说真的,论点相对简单(但是,是的,可能概述得很差,这是我的一个常见问题);如果您有兴趣,我可以帮助您理解它(或进一步混淆您......)
对于小负载因子,二次探测提供与线性探测相当的缓存性能(因为两者的单次碰撞行为相同),但在某些哈希事件中它不太可能陷入退化行为值是聚集在一起的。以上是关于冲突解决:二次探测与单独链接的主要内容,如果未能解决你的问题,请参考以下文章