怎么实现一个更全面的Golang版本的布谷鸟过滤器

发布时间:2021-03-11 11:47:43 作者:小新
来源:亿速云 阅读:285

这篇文章给大家分享的是有关怎么实现一个更全面的Golang版本的布谷鸟过滤器的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

布谷鸟过滤器

布谷鸟过滤器在网络上已经有很多的介绍文章了,这里不再做过多的介绍,只提一下要点,用于引出下面的内容

如果想要知道更多的细节,可以参考 原论文,或者查看我的 中文翻译版本

什么是布谷鸟过滤器?

是一种基于布谷鸟哈系算法实现的过滤器,本质上是存储了存储项哈希值的布谷鸟哈希表。

如果你了解布隆过滤器,你应该知道布隆过滤器原理是采用多种哈希方法,将存储项的不同哈希映射到位数组上,查询时通过对这些位进行检查来判断是否存在。

而布谷鸟过滤器是对存储项做哈希,将其哈希值取一定位数存储在数组中,查询时通过判断相等位数的哈希是否在数组中来判断是否存在。

为什么选择布谷鸟过滤器?

同样都是存储哈希值,本质上都是存储多位哈希,为什么布谷鸟过滤器更优?

优点有了,缺点呢?相比于布隆过滤器

优缺点都列出来了,我们再来总结一下。对于这种集合隶属测试问题,大部分情景都是读多写少的,而重复插入并没有什么意义,布谷鸟过滤器的删除虽然不完美但总好过没有,同时还有更优的查询和存储效率,应该说在绝大多数情况下其都是一个性价比更高的选择。

实战指南

细节实现

先说一下布谷鸟过滤器中的概念,过滤器是由很多桶组成的,每个桶存储插入项经过哈希计算后的值,该值只会存储固定位数。

过滤器中有 n 个桶,桶的数量是根据要存储的项的数量计算得来的。通过哈希算法我们可以计算出一个项应该存储在哪个桶中,此外每增加一个哈希算法,就可以对一个项产生一个候选桶,当重复插入时,会把当前存储的项踢到候选桶中去。理论上哈希算法越多,空间利用率越高,但实际测试使用 k=2 个哈希函数就可以达到 98% 的利用率了。

每一个桶会存储多个指纹,这受制于桶的大小,不同的指纹可能映射到同一个桶中。桶越大,空间利用率越高,但同时每次查询扫描同一桶中指纹数越多,因此产生假阳性的概率越高,此时就需要提高存储的指纹位数,用以降低冲突率,维持假阳性率。

在论文中提到了实现布谷鸟过滤器所需的几个参数,主要是

详细阅读论文,在第五章中作者依靠试验数据告诉了我们如何选择最合适的构建参数,我们可以得到如下结论

根据上述的理论依据,得出的相关实验数据有:

这样一来我们便可以确定如何选择参数来构建我们的布谷鸟过滤器了

首先哈希函数我们使用两个就足够了,这可以达到足够的空间利用率。根据我们需要的假阳性率,我们可以确定使用多大的桶大小,当然 b 的选择并不绝对,即使 r>0.002,你也可以使用 b=4 来启用半排序桶。之后我们可以根据 b 来计算为了达到目标假阳性率,我们需要的 f 的大小,这样所有的过滤器参数就确定了。

将上面的结论与布隆过滤器的每项 $1.44log_2(1/r)$ 对比,可以发现启用半排序时,当 r<0.03 左右,布谷鸟过滤器空间更小,若不启用半排序,则退化到 r<0.003 左右。

一些进阶解释

哈希算法的优化

虽然我们指定了需要两个哈希算法,但实际实现上我们使用一个哈希算法就足够了,因为在论文中提到了一种备选桶计算方法,第二个哈希值可以由第一个哈希值与该位置存储的指纹异或计算得来。如果你在担心我们还需要分别计算指纹的哈希和位置的哈希,我们可以只用一种算法制作 64 位的哈希,高 32 位用于计算位置,低 32 位用于计算指纹。

为什么半排序桶只能用于 b=4 的情况?

半排序的本质是对每个指纹取其四位,该四位可以表示为一个十六进制,对于 b 个指纹的四位存储就可以表示为 b 个 16 进制数,将其所有可能按顺序排列后,可以通过索引其位置来找到对应的排列,从而获取实际的存储值。

我们可以通过以下函数计算所有的情况种类数

func getNum(base, k, b, f int, cnt *int) {
    for i := base; i < 1<<f; i++ {
        if k+1 < b {
            getNum(i, k+1, b, f, cnt)
        } else {
            *cnt++
        }
    }}func getNextPow2(n uint64) uint {
    n--
    n |= n >> 1
    n |= n >> 2
    n |= n >> 4
    n |= n >> 8
    n |= n >> 16
    n |= n >> 32
    n++
    return uint(n)}func getNumOfKindAndBit(b, f int) {
    cnt := 0
    getNum(0, 0, b, f, &cnt)
    fmt.Printf("Num of kinds: %v, Num of needed bits: %v\n", cnt, math.Log2(float64(getNextPow2(uint64(cnt)))))}

在 b=4 时,总共有 3786 种排列,小于 4096,即用 12 位即可存储所有的排列索引,而如果直接存储所有指纹,则需要 4X4=16 位,这样节省了 4 位,即对每一个指纹节省了一位。

可以发现,在 b 为 2 时是否启用半排序需要存储的位数相同,没有意义。如果 b 太大则需要存储的索引也会急剧扩张,会在查询性能上有很大的损耗,因此 b=4 是一个性价比最高的选择。

此外编码存储四位指纹的选择是因为其刚好可以用一个十六进制表示,利于存储

使用半排序时的参数选择

使用半排序时,应保证 $ceil(b(f-1)/8)<ceil(bf/8)$,否则是否使用半排序占用的空间是一样大的

过滤器大小选择

过滤器的桶总大小一定是 2 的指数倍,因此在设定过滤器大小时,尽量满足 $size/α ~=(<) 2^n$,size 即为想要一个过滤器存储的数据量,必要时应选择小一点的过滤器,使用多个过滤器达到目标效果

Golang 实现

这部分主要是 Golang 库相关

在翻阅了 Github 上 cuckoofilter 的 golang 实现后,发现已有的实现都存在一些缺点:

因为自己的场景需要更优的空间和自定的假阳性率,因此移植了原论文的 C++ 实现,并做了一些优化,主要包括

感谢各位的阅读!关于“怎么实现一个更全面的Golang版本的布谷鸟过滤器”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

推荐阅读:
  1. 如何升级golang的版本
  2. 使用golang怎么实现一个分页算法

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

golang

上一篇:cmd命令实现数字雨的方法

下一篇:纯css实现小箭头或三角形标志的方法

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》