不能用[]bool实现位图,因其内存浪费8倍(1000万标签占10mb vs 1.25mb)、越界访问静默返回false导致无法区分“未设置”与“索引非法”、gc扫描慢3–5倍且缓存行利用率差一个数量级。

为什么不能用 []bool 实现位图
直接用 []bool 看似简单,但会带来三重硬伤:内存浪费、越界静默、GC 拖累。1000 万个用户标记,[]bool 占 10MB,而等效 []uint64 仅需约 1.25MB——不是“省一点”,是差整整 8 倍。更危险的是,b[10000000] 超长时不会 panic,而是静默返回 false,你根本分不清是“没设置”还是“索引非法”。GC 扫描大片 []bool 比扫描 []uint64 慢 3–5 倍,CPU 缓存行利用率也差一个数量级。
SetBit 和 GetBit 的位运算必须这么写
核心就三件事:索引用 uint64、位偏移用 uint、判断用 &。常见错误包括:
- 用
int当索引——32 位系统下超 2^31 就截断 -
wordIdx := i / 64没校验边界:if wordIdx >= uint64(len(b.bits)) { return }必须加 - 读取写成
(b.bits[wordIdx] >> bitIdx) & 1——负数右移补符号位,结果不可靠 - 正确写法是:
b.bits[wordIdx] |= (1 (注意 <code>1是uint64)和(b.bits[wordIdx] & (1
并发写同一 uint64 元素时怎么不出错
Go 没有单 bit 原子操作,多个 goroutine 写同一 uint64 元素会触发伪共享或数据撕裂。解决方案不是全局锁,而是分段控制:
- 按
uint64索引分桶,例如每 1024 个uint64为一桶 - 每个桶配独立
*sync.RWMutex或轻量atomic.Uint64标记状态 - 写前计算桶号:
shardIdx := wordIdx / bitsPerShard,确保同 ID 总落在同一桶 - 若只增不删且容忍少量重复(如布隆过滤器),可用
atomic.OrUint64(&b.bits[wordIdx], 1
标签基数超 1% 就该换 roaringbitmap
位图不是万能的。当某标签的去重值数量占总用户数比例 >1%(比如 “城市” 字段有 300 个取值、用户总量 3000 万),位图就会严重稀疏:内存暴涨、AND 运算变慢、缓存命中率骤降。此时应切到 roaringbitmap——它自动分块压缩,Cardinality() 是 O(1),ToArray() 才分配内存。手写 []uint64 只适合低基数场景(如 is_vip、status),且总量控制在 5000 万以内。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











