Bitmap不能直接用std::vector因底层按位存储导致指针操作受限、无原始内存视图,无法支持SIMD/原子操作;海量去重需连续对齐裸内存,推荐std::vector或mmap管理。

Bitmap 为什么不能直接用 std::vector 做海量去重
因为 std::vector 是特化容器,底层按位存储但接口伪装成“布尔数组”,导致无法直接取地址、不支持指针算术、迭代器行为异常;更关键的是它不提供原始内存视图,没法用 SIMD 或原子操作加速批量置位。海量数据(比如 10 亿整数)去重时,你真正需要的是可随机访问、可 mmap、可按字节/字对齐操作的裸内存块。
实操建议:
- 改用
std::vector或std::unique_ptr管理位图内存,确保内存连续且对齐 - 位索引计算必须用
index / 8取字节偏移、index % 8取位偏移,避免除法——改用index >> 3和index & 7 - 若数据范围已知(如 0~2^32−1),直接按最大值申请位图;若稀疏且范围大(如只含 100 万个 64 位 ID),优先考虑 Roaring Bitmap 等压缩结构,而非全量
uint8_t[512MB]
如何用 constexpr + 模板推导实现编译期位宽适配
硬编码 64 位或 8 位位图会限制场景:小数据用 64 位浪费空间,大数据用 8 位频繁 cache miss。正确做法是让编译器根据数据范围自动选择基础单元(uint8_t/uint32_t/uint64_t)和掩码策略。
示例核心逻辑:
template <uint64_t max_val>
struct Bitmap {
static constexpr size_t kSize = (MAX_VAL + 1 + 7) / 8;
std::unique_ptr<uint8_t> data_ = std::make_unique<uint8_t>(kSize);
<pre class="brush:php;toolbar:false;">__forceinline__ void set(uint64_t x) {
const size_t byte_idx = x >> 3;
const uint8_t bit_mask = 1U <p>};</p>
注意点:
-
MAX_VAL必须是编译期常量,否则模板无法实例化;运行时范围要用其他结构(如分段 Bitmap) - 不要在
set()里加边界检查——海量数据下分支预测失败代价远高于越界崩溃,上线前用测试数据校验范围 - 若需线程安全,别用
std::atomic包裹每个字节(太重),改用 CAS 循环或按 cache line 分片加锁
查重性能卡在内存带宽?试试 _mm256_testc_si256 批量探测
单次 get(x) 查一个 bit 没问题,但做“判断某批 10 万个数是否全部已存在”时,逐个查会触发上万次 cache line 加载。这时应把查询向量化:一次读入 32 字节(256 bit),用 AVX2 指令并行检测。
实操建议:
- 先对输入数组排序并去重(减少重复访存),再按 32 字节对齐分组
- 用
_mm256_loadu_si256((__m256i*)&bitmap_bytes[i])加载,配合_mm256_movemask_epi8提取 32 个字节的最高位成掩码整数 - 注意:AVX2 在某些老 CPU(如 Intel Haswell 以前)不支持,生产环境需运行时
__builtin_cpu_supports("avx2")检测 - 若数据局部性差(随机地址),向量化收益极小,此时不如用布隆过滤器预筛
磁盘级 Bitmap:用 mmap 避免 std::bad_alloc 的真实写法
10 亿个 int(32 位)对应位图约 125MB,看着不大,但若处理 IPv4 全空间(2^32 bit ≈ 512MB)或用户 ID(int64_t 范围则不可行),malloc 会失败。这时候必须绕过堆,直连文件映射。
关键代码片段:
int fd = open("bitmap.dat", O_RDWR | O_CREAT, 0644);
ftruncate(fd, bitmap_size_bytes);
uint8_t* ptr = static_cast<uint8_t>(mmap(nullptr, bitmap_size_bytes,
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
// 后续所有 set/get 直接操作 ptr[index>>3] |= (1 <p>容易踩的坑:</p>
<ul>
<li>
<code>mmap</code> 后必须调用 <code>msync(ptr, size, MS_SYNC)</code> 才能保证落盘,否则进程崩溃就丢数据</li>
<li>多进程共享时,<code>MAP_SHARED</code> 是必须的,<code>MAP_PRIVATE</code> 写时复制会导致各自副本</li>
<li>Linux 下 <code>/proc/sys/vm/max_map_count</code> 限制总映射区数量,大数据服务需提前调高(如设为 65536)</li>
</ul>
<p>实际用起来最麻烦的不是算法本身,而是数据范围是否稳定、内存是否真的够、以及 mmap 后忘记 <code>msync</code> 导致静默丢数据——这些地方一出错,结果既不报错也不正确。</p></uint8_t>
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











