位运算实现高效位数组交并集需确保字长、总位数和内存布局完全一致,否则易出错;应避免std::bitset隐式拷贝,采用原地操作、分块跳过全零字及运行时cpu特性检测等优化手段。

用位运算直接对齐字长做 & 和 |
交集就是 &,并集就是 |,前提是两个位数组底层存储结构完全一致:相同字长(如都是 uint64_t)、相同总位数、相同内存布局。一旦对齐,单条指令就能处理 64 个 bit,比逐 bit 比较快 64 倍。
常见错误是拿 std::bitset 和自定义 uint64_t buf[16] 直接运算——它们内存不连续或对齐方式不同,会出错或崩溃。
- 必须确保两个位数组都按
uint64_t分块,且块数相等(不足补零) - 用
reinterpret_cast强转指针前,先检查alignof(uint64_t)和实际地址是否对齐,否则触发未定义行为 - 若总位数不是 64 的倍数,最后一块需掩码处理:比如共 100 位,则第 16 块只用低 36 位,计算后 & 上
0x0000000F'FFFFFFFF
避免 std::bitset::operator& 的隐式拷贝开销
std::bitset 的 operator& 返回新对象,内部调用 std::array 的逐字复制;在循环中高频调用(如每帧算一次交集)会导致明显 cache miss 和分配抖动。
更高效的做法是手写原地操作函数:
void bitwise_and_inplace(uint64_t* dst, const uint64_t* src, size_t n_words) {
for (size_t i = 0; i
- 传入
dst和src指针,不构造临时对象 - 编译器能自动向量化(GCC/Clang 加
-O2 -mavx2后常生成vandpd指令) - 若
n_words很大(>1024),可分段加prefetch:对src[i+4]提前加载
稀疏场景下用 popcnt + 分块跳过全零字
当两个位数组都极稀疏(比如 1M 位中只有几十个 1),直接扫全部字很浪费。先用 __builtin_popcountll 快速判零,跳过全零块。
交集结果必然只出现在双方都非零的字里;并集则只需合并非零字索引再去重。
- 对每个字调用
if (__builtin_popcountll(a[i]) && __builtin_popcountll(b[i]))再计算交集 - 现代 CPU 上
popcnt指令延迟仅 1–3 cycle,远低于访存延迟 - 注意:
__builtin_popcountll(0)安全,无需额外判零;但 MSVC 的_mm_popcnt_u64需启用/arch:AVX2
跨平台兼容时慎用 BMI1/BMI2 指令
像 _tzcnt_u64(找最低置位)或 _pdep_u64(并行位展开)虽快,但依赖 CPU 支持。在无 BMI 的机器(如老款 Intel Core2 或部分 AMD APU)上会 crash。
生产环境建议 fallback 到 portable 实现:
- 检测运行时 CPU 特性:
__builtin_ia32_cpu_supports("bmi1")(GCC 12+)或用cpuid指令手动查 - 交集逻辑本身不依赖 BMI;只有“提取交集位置列表”才需要 CTZ 类指令
- 若只求布尔结果(“是否有交集?”),用
!((a & b) == 0)即可,连 popcnt 都不用
最易被忽略的是:位数组长度不对齐时,最后一块的高位 padding bit 必须清零,否则 & 可能误触发高位 1 导致交集结果污染——这在调试时很难复现,尤其当测试数据刚好避开 padding 区域。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











