这一篇讲的是布谷过滤器(cuckoo fliter),这个名字来源于更早发表的布谷散列(cuckoo hash),尽管我也不知道为什么当初要给这种散列表起个鸟名=_=

由于布谷过滤器本身的思想就源自于布谷散列,那么我们就从布谷散列开始说它的设计思想。产生布谷散列表的一个重要背景是人们对于球盒问题的分析:给定N个球,随机的放在N个盒子里,在装球最多的盒子里,球的个数的期望是多少?答案是\(\Theta (logN/loglogN)\),这个问题其实就是散列表装填因子为1时的情况分析。后来有一天,人们发现:每次放球的时候,如果随机选择两个盒子,将球放到当时较空的那个盒子里,那么这个期望就变成了\(\Theta (loglogN)\),这个界小于之前的界,这给了布谷散列表作者启发。

一个布谷散列表通常有两张(一般来说)表,分别有一个对应的Hash函数,当有新的项插入的时候,它会计算出这个项在两张表中对应的两个位置,这个项一定会被存在这两个位置之一,而具体是哪一个却不确定。

下图是一个布谷散列表的初始化示意图:

现在我们假设有一些项要存入散列表,其每个项都有其对应的两个位置,先插入第一项A

由于插入A的时候其两个候选位置(0,2)都没有占用,所以选择第一张表或者是第二张表都可以,我们在这里默认先选择第一张表,然后插入第二项B

我们看到原来的A的位置被B占用,而A被“踢”到它的备选位置表二的2号位置上了,这就是当发生位置冲突时,布谷散列表的处理逻辑,后来的数据项将会把之前占用的项踢到另一个位置上。我们接下来插入第三项C



没有冲突,顺利搞定,接着插入D



D成功的把C踢走了,其实看到这里读者应该在猜想,会不会有一种情况,即被踢走的数据的另一个备选位置也被占用了,这样怎么办?答案是继续踢,一个踢一个,直到大家都找到自己合适的归宿为止。那么如果发现出现了循环怎么办?答案是GG,这代表布谷散列表走到了极限



插入E



这里就发生了多次替换的情况,F代替了E,E代替了A,A代替了B,B找到了空余的位子

读者可以考虑一下,如果这个时候要想插入一个“G”,其备选位置是1,2,这样的话会出现什么情况?

好了,布谷散列表大概介绍完了,现在该布谷过滤器了。布谷过滤器的主要也是通过布谷散列来实现的,其主要变化是:

1.我们不将原来的数据完整的存进来,作为过滤器,当然要省点空间,选用的办法设计一个指纹,将比较大的原数据变成了一个个指纹串,这样就大大节省了空间。

2.由于第一点所说,存储的不是原数据,那么在替换位置的时候,我们需要再次计算原来的数据的备选位置,这样一来布谷散列表的方法就失效了。在这里,作者设计了一个方法,他将两个Hash函数变成了一个Hash函数,第一张表的备选位置是Hash(x),第二张表的备选位置是\(Hash(x) \oplus hash(fingerprint(x))\),即第一张表的位置与存储的指纹的Hash值做异或运算。这样做的优点就是不用再另外存储元素的备选位置,在替换时,可以直接用异或来计算出其另一个备选位置。(读者可以想想如何通过表二的位置计算出元素在表一中的位置)

3.插入时,优先选择空位置,而不是尽可能的踢走其他元素。

插入伪代码如下:

  1. Algorithm 1: Insert(x)
  2. f = fingerprint(x)
  3. i1 = hash(x)
  4. i2 = i1 xor hash(f)
  5. if bucket[i1] or bucket[i2] has an empty entry then
  6. //只要有空位就先插入空位里
  7. add f to that bucket
  8. return Done
  9. i = randomly pick i1 or i2
  10. for n=0;n<MaxNumKicks;++n
  11. randomly select an entry e from bucket[i];
  12. swap f and the fingerprint stored in entry e;
  13. i = i xor hash(f)
  14. if bucket[i] has an empty entry then
  15. add f to bucket[i]
  16. return Done
  17. return Failure // 已经出现循环情况了

查找伪代码如下:

  1. Algorithm 2: Lookup(x)
  2. f = fingerprint(x)
  3. i1 = hash(x)
  4. i2 = i1 xor hash(f)
  5. if bucket[i1] or bucket[i2] has f then
  6. return True
  7. return False

删除伪代码如下:

  1. Algorithm 3: Delete(x)
  2. f = fingerprint(x)
  3. i1 = hash(x)
  4. i2 = i1 xor hash(f)
  5. if bucket[i1] or bucket[i2] has f then
  6. remove a copy of f from this bucket
  7. return False

删除这部分值得注意,当被删除元素的另一个备选位置有其他元素的指纹的时候,我们不能确定到底哪一个是要删除的元素,其实我们也不去关心到底是不是要删除的元素,我们直接删除掉其中的一个。这样一来,我们其实并没有真正的删除掉元素,为什么这么说,因为当你删除掉这个元素的时候我们再查找这个元素,按照算法来看我们还是一样能检索出来这个元素在我们的布谷过滤器里,这就是假阳率的其中一个来源(还有一个重要来源是指纹构造的重复,即多个元素产生相同指纹)

下面我们来分析一下布谷过滤器的平均每个元素占用的比特数,设每个桶里装\(b\)个指纹,要求错误率的上界为\(\epsilon\),\(f\)为指纹长度。

错误率的上界 = \(1-(1-1/2^f)^{2b} \approx 2b/2^f\)

那么这个上界要求小于要求的上界,即\(2b/2^f \le \epsilon\),得到

\(f \ge log_2^{2b/\epsilon} = log_2^{1/\epsilon} + log_2^{2b}\)

则平均每个元素占用的比特数为\(C \le (log_2^{1/\epsilon} + log_2^{2b}) / \alpha\)

在原论文中,作者其实后面还做了一个比较强行的优化,在此不提,后面设计其他过滤器的作者也没有把这个优化算数。。。。不过作者提到了在实际测试中,他们发现当b=4的时候是空间性能最好的情况,所以一般说来,我们直接把b当做常数4,代入到前面算出来的公式中,\(C \le (log_2^{1/\epsilon} + 3) / \alpha\)

布谷过滤器就说到这,布谷过滤器在错误率小于3%的时候空间性能是优于布隆过滤器的,而这个条件在实际应用中常常满足,所以一般来说它的空间性能是要优于布隆过滤器的。而且,布谷过滤器按照普通设计,只有两个桶,在查找的时候可以确保两次访存就可以做完,相比于布隆过滤器的K个Hash函数K次访存,在数据量很大不能全部装载在内存中的情况下,多一次访存那么时间上就输了。不过说完优点,布谷过滤器也有其相应的缺点,当装填因子较高的时候,容易出现循环的问题,即插入失败的情况,到这时就很难办。另外,它还有跟布隆过滤器共有的一个缺点,就是访问空间地址不连续,通常可以认为是随机的,这样严重破坏了程序局部性,对于Cache流水线来说非常不利,这为之后的过滤器设计埋下了一个伏笔。

过滤器系列(二)—— Cuckoo filter的更多相关文章

  1. WEB API 系列(二) Filter的使用以及执行顺序

    在WEB Api中,引入了面向切面编程(AOP)的思想,在某些特定的位置可以插入特定的Filter进行过程拦截处理.引入了这一机制可以更好地践行DRY(Don’t Repeat Yourself)思想 ...

  2. Web API系列(二) Filter的使用以及执行顺序

    在WEB Api中,引入了面向切面编程(AOP)的思想,在某些特定的位置可以插入特定的Filter进行过程拦截处理.引入了这一机制可以更好地践行DRY(Don’t Repeat Yourself)思想 ...

  3. Wireshark入门与进阶系列(二)

    摘自http://blog.csdn.net/howeverpf/article/details/40743705 Wireshark入门与进阶系列(二) “君子生非异也,善假于物也”---荀子 本文 ...

  4. Mina 系列(二)之基础

    Mina 系列(二)之基础 Mina 使用起来多么简洁方便呀,就是不具备 Java NIO 的基础,只要了解 Mina 常用的 API,就可以灵活使用并完成应用开发. 1. Mina 概述 首先,看 ...

  5. struts2官方 中文教程 系列二:Hello World项目

    先贴个本帖的地址,免得其它网站被爬去了struts2入门系列二之Hello World  即 http://www.cnblogs.com/linghaoxinpian/p/6898779.html ...

  6. Redis总结(五)缓存雪崩和缓存穿透等问题 Web API系列(三)统一异常处理 C#总结(一)AutoResetEvent的使用介绍(用AutoResetEvent实现同步) C#总结(二)事件Event 介绍总结 C#总结(三)DataGridView增加全选列 Web API系列(二)接口安全和参数校验 RabbitMQ学习系列(六): RabbitMQ 高可用集群

    Redis总结(五)缓存雪崩和缓存穿透等问题   前面讲过一些redis 缓存的使用和数据持久化.感兴趣的朋友可以看看之前的文章,http://www.cnblogs.com/zhangweizhon ...

  7. 爬虫系列(二) Chrome抓包分析

    在这篇文章中,我们将尝试使用直观的网页分析工具(Chrome 开发者工具)对网页进行抓包分析,更加深入的了解网络爬虫的本质与内涵 1.测试环境 浏览器:Chrome 浏览器 浏览器版本:67.0.33 ...

  8. Tomcat 内存马(二)Filter型

    一.Tomcat处理请求 在前一个章节讲到,tomcat在处理请求时候,首先会经过连接器Coyote把request对象转换成ServletRequest后,传递给Catalina进行处理. 在Cat ...

  9. 图机器学习(GML)&图神经网络(GNN)原理和代码实现(前置学习系列二)

    项目链接:https://aistudio.baidu.com/aistudio/projectdetail/4990947?contributionType=1 欢迎fork欢迎三连!文章篇幅有限, ...

  10. 前端构建大法 Gulp 系列 (二):为什么选择gulp

    系列目录 前端构建大法 Gulp 系列 (一):为什么需要前端构建 前端构建大法 Gulp 系列 (二):为什么选择gulp 前端构建大法 Gulp 系列 (三):gulp的4个API 让你成为gul ...

随机推荐

  1. Google Now 'not available in your country'

    Google Now 'not available in your country' Don't know how to cope with this problem.

  2. centos Linux系统日常管理1 cpuinfo cpu核数 命令 w, vmstat, uptime ,top ,kill ,ps ,free,netstat ,sar, ulimit ,lsof ,pidof 第十四节课

    centos Linux系统日常管理1  cpuinfo cpu核数   命令 w, vmstat, uptime ,top ,kill ,ps ,free,netstat ,sar, ulimit ...

  3. oracle基于3种方法的大数据量插入更新

    过程插入更新的3种方法: a.逐条检查插入或更新,同时执行插入或更新 b.逐条merge into(逐条是为了记录过程日志与错误信息) c.基于关联数组的检查插入.更新,通过forall批量sql执行 ...

  4. C++模拟Http/Https访问web站点

    一.概述 1.Http与Https的区别与联系 在OSI参考模型中Http与Https均属于应用层协议.Http即Hypertext Transfer Protocol,超文本传输协议:而Https为 ...

  5. 解读webpack的bundle.js

    可能就是好奇心略重了,读了一下webpack打包后的bundle.js的代码,复杂的模块可能读不懂,但简单的hello world模块我还是能看懂的.没什么目的,就是想通过几个简单的模块,一条简单的w ...

  6. HTML5开发——轻量级JSON存储解决方案Lawnchair.js

    Lawnchair是一个轻量级的移动应用程序数据持久化存储方案,同时也是客户端JSON文档存储方法,优点是短小,语法简洁,扩展性比较好. 现在做HTML5移动应用除了LocalStorage的兼容性比 ...

  7. 2.6 The Object Model -- Bindings

    一个binding在两个属性之间创建一个链接,当一个改变时,另外一个被自动更新为一个新的值. bindings可以在同一个对象中连接两个属性,或者用在两个不同的对象中. 不像大多数框架一样包含某种形式 ...

  8. 临时表单导出Excel

    function ExportExcel(url, vals) { var form = jQuery("<form action='" + url + "' me ...

  9. cocos2dx 3.x 区域画图

    .h文件 bool onTouchBegan(cocos2d::Touch *pTouch, cocos2d::Event *pEvent); void onTouchMoved(cocos2d::T ...

  10. struts2.1.8 spring2.5.6 hibernate3.3G 依赖jar包

    ----struts2.1.8---- struts2-core-2.1.8.1.jar struts2核心包 struts2-json-plugin-"} struts2-spring-p ...