RoaringBitmap更好的位图压缩算法分析

项目github源码:https://github.com/lemire/RoaringBitmap
论文Better bitmap performance with Roaring bitmaps http://arxiv.org/pdf/1402.6407.pdf

比较流行的位图压缩算法包括WAH, EWAH, Concise等,它们是基于RLE(Run-Length Encode,运行长度编码)来压缩的。RoaringBitmap可以比RLE的压缩算法更高。

Roaring Bitmap使用在 Apache Spark (https://spark.apache.org/) 和 Druid.io (http://druid.io/). Apache Lucene (http://lucene.apache.org/) 也使用 Roaring bitmaps, 虽然他们自己独立的实现。

Roaring Bitmap是将32位的整数分割成2的16次方个整数的数据块,来共享相同的16个最高有效位。使用专门的容器来保存它们的16个最低有效位。

当一个数据块整数不超过4096个时,使用一个16位整数的有序数组(在java中使用short类型数组)。当超过4096个整数时,我们使用2^16位的位图(在java中使用long类型数组)。因此我们有两种类型的容器,对于稀疏数据块的数组容器(ArrayContainer)和对于密集数据块的位图容器(BitmapContainer)。阈值4096保证容器的级别,每个整数使用不超过16比特。使用位图容器时,使用2^16来表示超过4096(=2^12)个整数,少于16比特/整数(2^16 / 2^12 = 2^4 = 16,如果值都充满long数组,最理想情况下1比特/整数)。使用数组容器时使用精确的16比特/整数。

为什么选择4096这个阈值呢?因为小于4096时,位图容器可能大于16比特/整数,大于4096时,数组容器会超过2^16(2^12 * 16 = 2^16),占用空间显然超过2^16这个低16位表示的数的容量。一句话,整数基数较小时,使用数组更省空间,基数较大时,使用bitmap更省空间。

这些容器保存在一个共享16个最高有效位的动态数组中:它们作为一级索引。使用数组保证高16位有序。我们认为一级索引一般很小。当n=1 000 000时,它至多包含16个实体。因此它应该保存在CPU缓存中。容器本身不应该使用超过8KB。


[img]http://dl2.iteye.com/upload/attachment/0111/4500/f3077b97-213e-33b0-b24b-60ba3a259291.png[/img]

为了描述数据结构,考虑前1000个62的倍数(0, 62, 62*2, 62*3...62*1000),在2^16到2^16+100之间的所有整数,以及2 * 2^16至3 * 2^16之间的所有偶数。当对这个列表编码时,使用Concise格式,我们使用一个32位的填充字来对1000个62倍数的每一个字,使用两个额外的填充字来包含这个列表中在2^16到2^16+100之间的整数,以及在2 * 2^32到3 * 2^32之间的偶数保存成字面词(literal words)。在Roaring格式中,62的倍数和[2^16; 2^16 + 100) 之间的整数都使用16个比特表示一个整数的数组容器。[2 * 2^16; 3 * 2^16)之间的偶数保存在2^16个比特的位图容器中。

每个Roaring容器使用一个计数器记录它的基数(整数的数目)。因此计算Roaring位图的基数可以很快完成: 它需要最多累加2^16个计数器。它同样在支持排序和选择查询方面比典型的位图更快变得可能:排序查询累加集合比特在[0:1]范围内的数量,而选择查询寻找第i个集合比特的位置。

由于容器和动态数组导致的负载意味着我们的存储用量会超过16比特/整数。然而,只要容器的数量相比整数的总量很少,我们就不会使用超过16字节/整数。我们假设这里有比整数少得多的容器。更准确的说,我们假设密度一般会超过0.1%即n/|S|>0.001。当应用程序使用低密度(少于0.1%)的整数集合,位图就不是一种适当的数据结构。

展示出来的Roaring数据布局故意简单的。一些变化是可能的。对于非常稠密的位图,当每个容器中包括超过2^16 - 4096个整数,我们可以保存0比特的位置而不是2^16个比特的位图。而且我们可以更好的压缩连续整数的序列。我们留下这些可能性的研究作为将来的工作。

去检查一个32位的整数x是否存在,我们首先去查找x/2^16关联的容器,使用二分查找。如果一个位图容器被找到,我们访问第x对2^16余数个的比特。如果一个数组容器被找到,我们再次使用二分查找。

我们类似地插入和删除一个整数x。我们首先寻找相应的容器。当被找到的容器是一个位图,我们设置相应比特的值并更新基数。如果我们找到一个数组集合。我们使用二分查找并用线性时间的插入或删除。

当删除一个整数时,位图容器可能会变成一个数组容器如果基数达到4096。当增加一个整数,一个整数容器可能变成一个位图容器当它的基数超过4096。当这个发生时,创造新的容器并包括更新的数据而旧的容器被丢弃。从一个数组容器转换到位图容器是通过创建一个新的初始化为0的位图容器,并设置相应的比特。当转换一个位图容器到数组容器,我们提取出集合比特的位置,使用一个优化的算法。

逻辑运算:
我们在Roaring位图上实现了不同的操作,包括并集(OR操作)和交集(AND操作)。两个Roaring位图之间的位操作由迭代和比较一级索引上的16个高比特位的整数。为了更好的性能,我们维护排序的一级数组。在每次迭代中比较两个key。相同的,相应容器的二级逻辑操作也会操作。这总会生成一个新的集合。如果容器不是空的,它会添加到公共key上。然后迭代定位在一级数组加一。如果两个键不相等,包括最小键的数组递增一个位置,如果执行并操作,最低的键和一份对相应容器的拷贝添加到答案中。当计算并集时,我们重复直到两个一级数组被遍历完。当计算交集时,只要有一个数组遍历完成就终止。

排序的一级数组允许在O(n1 + n2)时间内进行一级比较,而n1和n2是对应的两个比较的数组的长度。我们也维护数组容器有序也是基于相同的优势。而容器可以用两种不同的数据结构,位图和数组,两个容器之间的一个逻辑并或交涉及到下面三种场景的一种:

涉及到三种容器的运算:
位图与位图,待完善。
位图与数组,待完善。
数组与数组,待完善。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值