Bitmap的原理
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Bitmap的原理相关的知识,希望对你有一定的参考价值。
参考技术A 在网上有一道面试题大概意思就是: 存在十亿个数字,现在需要知道哪些数字没有出现在里面. 这个问题你可能觉得一个for循环就能解决,但是实际上你可能忽略了一个问题,那就是10亿个数字需要占据的内存空间.例如在java中要存下10亿个数字需要多大空间呢?假设使用int存储,一个int占32个位也就是4个字节.存放10亿个数字则需要: 10亿*4/1024/1024/1024 = 4G左右 .面对上面的问题我们肯定不能使用这种方式存储.而使用我们的Bitmap就能很好的解决上面的问题.Bitmapi简单的说就是使用bit(位)来存储状态,适合用来存储大规模的数据,但是状态又不是很多的情况.举例来说,如果我现在有 1,3,4,2,7 这五个数字,如果我们使用5个int来存储则需要20个字节的空间.但是我现在使用1字节就能存储上面的五个数字.如下图所示,1个字节可以用8位表示,1位有0和1两种值分别用来表示该位上是否有值,而位的索引用来表示数字(图中以从左到右的顺序计算).
而它的缺点同样也比较明显.它的一个位只能存储一个数据,对于重复的数据无法记录.如果数据分布很稀疏,会造成空间的浪费.例如,如果现在只有1,3,1000000000这个三个数据,它有很多空间会被浪费掉.
BitMap在java中提供了BitSet的实现,同时在我们平常使用的Redis中也有实现.它很适合用来做大量的用户统计.例如统计登录人数,签到,在线等等.这些需求都可以通过redis的bit相关指令很简单的就能实现.
例如,统计网站的在线人数:
上面的方式不仅简单,而且就算用户数量很大也能又快又好的完成.
qemu HBitmap原理
一:磁盘位图HBitmap分析
使用unsigned long已经能够胜任bitmap的实现,但是当bitmap比较大的时候,它的操作效率很低。像BloclDriver维持bitmap,磁盘文件的每个块都对应一个bit为,那么这张bitmap表是很大的。如果在热迁移过程中,需要将镜像文件热迁移到目标宿主机上,每次查询bitmap的效率很低。因此,QEMU针对磁盘镜像文件设计了HBitmap数据结构。
HBitmap数据结构如下:
struct HBitmap {
uint64_t size;
uint64_t count;
int granularity;
HBitmap meta;
unsigned long levels[HBITMAP_LEVELS];
uint64_t sizes[HBITMAP_LEVELS];
};
uint64_t size : 虚拟机磁盘大小,计算方式:size = (size + (1ULL << granularity) - 1) >> granularity。 如虚拟镜像大小为1G,size = (102410241024 + (1ULL<<16) -1 ) >>16 即size=16385
int granularity : 12-16,默认是16
unsigned long *levels[HBITMAP_LEVELS] : 保存每一层的位图
uint64_t sizes[HBITMAP_LEVELS] : 记录levels每一层对应的数组大小
磁盘50g时,HBitmap数据结构对应的信息:
{
size = 819200,
count = 4,
granularity = 16,
meta = 0x0,
levels = {0x56131ac3cce0, 0x56131ac3ccc0, 0x56131ac3cca0, 0x56131a70e090, 0x56131a70e060, 0x56131ac62eb0, 0x56131b630400},
sizes = {1, 1, 1, 1, 4, 200, 12800}
}
uint64_t sizes[HBITMAP_LEVELS] 每层设置的最大数组,以上层数组的值向右偏移BITS_PER_LEVEL=6位,算法如下:
for (i = HBITMAP_LEVELS; i-- > 0; ) {
size = MAX((size + BITS_PER_LONG - 1) >> BITS_PER_LEVEL, 1);
hb->sizes[i] = size;
hb->levels[i] = g_new0(unsigned long, size);
}
HBitmap层次结构图,总共7层,下层记录上层的修改bit位,当上层某bit位置为1时,下层会相应的位记录
二:根据偏移地址在HBitmap中设置
例如,虚拟机在磁盘的起始位置start=2292187136写入count=262144字节
-
计算第6层word位置(起始word和结束word)
startpos = 2292187136 >>16>>6 即start = 546,在第6层的第546个位置开始
lastpos = (start + count - 1) >>16 >>6。在第6层的第546个位置结束
lastpos = (2292187136 + 262144 - 1) >> 16 >>6 = 546 -
计算第6层word中对应的bit位
firsh = start >> 16 = 2292187136 >> 16 = 34976
last = (start + count - 1) >> 16 = (2292187136 + 262144 - 1) >> 16 = 34979
mask = 2UL << (last & (BITS_PER_LONG - 1))
mask -= 1UL << (firsh & (BITS_PER_LONG - 1))
mask = 2UL << (34979 & (64 - 1)) - (34976 & (64 - 1)) = 0xf00000000
转换成二进制,在word中的第32, 33,34,35位分别写入1 -
第6层有bit位写入为1时,继续设置第5层
先计算第5层的word位(起始word和结束word)
startpos = 546 >> 6 = 8
lastpos = 546 >> 6 = 8
即写入第5层的第8个word位置计算第5层word中对应的bit位 mask = 2UL << (546 & (64 - 1)) - (546 & (64 - 1)) = 0x400000000 转换成二进制,在word中第34位写入1
- 如果该层有bit为修改,依次设置下层,直到第0层
三: 根据地址获取对应的bit位
例如,虚拟机在磁盘的起始位置start=2292187136,计算该起始位置对应的bit位是否设置为1
bit = 1UL <<(start >>hb->granularity) & (BITS_PER_LONG - 1)
bit = 1UL <<((2292187136 >> 16) & (64 -1)) = 0x100000000
转换成二进制,在word中第32位为1,与在HBitmap中设置的一致。
具体实现见函数hbitmap_get
以上是关于Bitmap的原理的主要内容,如果未能解决你的问题,请参考以下文章