关于位图存储与数组下标存储的探讨
最近在看《编程珠玑》,书中的第一章提到了位图。位图存储使用二进制位的位数信息,来表示数字。例如,存储1、3、4、6、8,这五个数字, 可以使用<1, 0, 1, 1, 0, 1, 0, 1>这样的向量来表示,这样可以大大减少存储空间, 只用了一个字节(8bit),就可以存储原来需要的8个数字才能表示的信息。
当我跟朋友谈到这个方法时,他却产生了质疑,他觉得这样不如直接按照数组下标存储来的快。他认为数组下标直接存储,直接一步完成,二位图则需要每次位运算寻址,会非常费时间。于是我们就决定写一个程序,实际跑一下看看。
这是位图的代码
typedef struct {
uint32_t *bits;
size_t size;
} Bitmap;
Bitmap *create_bitmap(size_t max_num) {
size_t num_words = (max_num + 31) / 32; // 计算需要多少个32位字
Bitmap *bm = malloc(sizeof(Bitmap));
if (!bm) {
perror("Bitmap allocation failed");
exit(EXIT_FAILURE);
}
bm->bits = calloc(num_words, sizeof(uint32_t)); // 分配约 1亿位 ≈ 12.5MB
bm->size = max_num;
if (!bm->bits) {
perror("Bitmap bits allocation failed");
exit(EXIT_FAILURE);
}
return bm;
}
void bitmap_set(Bitmap *bm, int num) {
size_t word_idx = num / 32;
size_t bit_idx = num % 32;
bm->bits[word_idx] |= (1U << bit_idx);
}
int bitmap_get(const Bitmap *bm, int num) {
size_t word_idx = num / 32;
size_t bit_idx = num % 32;
return (bm->bits[word_idx] >> bit_idx) & 1U;
}
位运算把每个字节大小的内存当作为一组,使用除法判断组数, 再用对8取余,得出每组的位次
下面是数组的代码
#define N 10000000 // 处理 1000 万个整数
#define MAX_NUM 100000000 // 数字范围:0 ~ 1亿
void array_set(int *arr, int num) {
arr[num] = 1;
}
int array_get(const int *arr, int num) {
return arr[num];
}
// 初始化数组(全部置0)
int *create_array() {
int *arr = calloc(MAX_NUM, sizeof(int)); // 分配 1亿个int ≈ 400MB
if (!arr) {
perror("Array allocation failed");
exit(EXIT_FAILURE);
}
return arr;
}
结果确实出乎了我们的意料,总结来说如下
- 当数据导向在大小在10MB一下时, 数组比位图略有优势
- 当数据大于10MB时, 位图大约要快5-10倍
- 然而当数据非常大时,比如1一个数, 虽然位图还是要快于数组,但两者没那么明显了
这是因为
- 在很小数据的时候,cpu缓存可以完全存下数据,而数组的指令开销更少,所以会略快
- 而当数据在大时,数组就无法在缓存中完全存储了,而位图还可以。于是cpu的缓存命中率就会下降,cpu需要从内存中加载数据,我们都知道cpu缓存的速度比内存快几十倍,所以数组方式的就慢了很多
- 但是当数据非常大时, 这时位图也无法完全缓存了,在这种情况下,影响程序运行速度的主要因素就是从内存加载的速度了,这时候两者时间接近。但是位图的存储更加紧凑,命中概率更高,所以还是会快一些。
何时选择哪种方案?
用数组: 当数字范围小(例如 0~1000),或需要频繁读取且容忍内存浪费时。
用位图: 当处理海量数据(如爬虫去重、数据库索引),或数字范围大但稀疏分布时。
实际应用案例:
- Linux 内核页表 使用位图管理内存页
- Redis 的 Bitmap 类型 用于高效存储布尔值
总结
实践是检验真理的唯一标准 我们对于计算机的了解不足,忘记了计算机是一个复杂的系统,程序运行的时间受到多种因素影响。而我们都以理想的角度去理解,就导致了形而上学的认识。
收获
- 认识到了cpu缓存命中带来的影响
- 对不同数据结构在不同条件下的性能影响