2026/10/26 10:24:17

记一次内存排查:千万级布尔标签位图,我从 list 一路换到 BoolHybridArr

记一次内存排查:千万级布尔标签位图,我从 list 一路换到 BoolHybridArr 记一次内存排查千万级布尔标签位图我从 list 一路换到 BoolHybridArr先说背景不是安利是上周线上一个真实的内存告警。我维护的服务里有一份用户标签位图长度上千万每个位置对应用户是否命中某个标签。绝大多数位置是 False只有零星 True。最顺手的写法当然是flags[i%1000foriinrange(10_000_000)]功能一直没问题直到监控里进程 RSS 缓慢往上爬。我用 tracemalloc 加上 pympler 定位了一圈最后发现占大头的不是缓存而是这个布尔标签列表——Python list 里每个槽位存的都是对 True/False 单例的引用槽位本身 8 字节一千万个槽位就是八十 MB 起步而我真正想表达的信息量其实只有 1 bit/个。等于拿装大象的箱子装开关。我依次试过的几条路1) bytearray确实省一个元素一个字节flagsbytearray(i%1000foriinrange(10_000_000))但我在业务代码里踩的坑是它语义上是整数flags[0]拿回来是 0/1 而不是 False/True下游一堆if flags[i] is True、以及按布尔做位运算、和 numpy 互转的地方我都得自己包一层转换。写着写着发现我在重新发明一个布尔数组。2) numpy 的 bool 数组稠密存储随机访问快我本来以为这就是答案importnumpyasnp flagsnp.fromiter((i%1000foriinrange(10_000_000)),dtypebool)问题在于我的数据是极度稀疏的——为了那 1% 的 True我得为 100% 的位置付内存。而另一个业务场景里数据又几乎全是 True、只有零星 False等于还得反过来再写一套。更麻烦的是这份数据的密度会随业务动态变化今天稀疏、明天某次全量刷新后可能就稠密了。3) 第三方 bitarray也试过。它本身没什么问题只是不匹配我的场景我在 numpy 2.1 环境里做np.asarray(ba, dtypebool)时碰到了弃用/转换的坑中间还得自己走一遍.tobytes()。更关键的是稀疏和稠密之间自动切换这件事没有谁帮我做——我不想在业务代码里到处散布if 密度 阈值: 用稀疏 else: 用稠密。换了个思路后来在 PyPI 上搜到 bool-hybrid-array。吸引我的不是它功能多而是它正好对应我那个不想自己判断稀疏稠密的诉求一个类内部在密集存储和稀疏存储之间自动切换。python-muv pipinstallbool-hybrid-arrayfrombool_hybrid_arrayimportBoolHybridArr flagsBoolHybridArr([i%1000foriinrange(10_000_000)])用起来是普通数组的手感索引、切片、赋值、any/all/count 都直接能用print(flags[0])# Trueprint(flags.count(True))flags[2]False我最在意的两件事它都给了接口一是能直接看内存账二是能在批量改动后手动触发一次重排flags.memory_usage(detailTrue)# 占用、相对 list/numpy 省多少、是否建议优化flags.optimize()# 按当前密度重新决定存储方式我在自己机器上Python 3.12数据为 10% True 的一千万样本跑出来的量级和 README 给的100 万元素、约 90% 节省基本是同一档相对原生 list 明显省随机单点读取速度没有可感知的下降。但这个数强烈依赖密度和机器建议你拿真实数据用 memory_usage() 自己量别只信我转述。用了一段时间也说说我踩回来的代价只讲收益不诚实这几点是我实际遇到、选型前应该知道的适合谁不适合谁我的中立结论它现在已经不只是一个布尔数组了9.x 线还加了二维 BHA_List、numpy 互转、int_array/float_array/struct_array 子包、位运算以及 .bha/.bhax 序列化。但我个人只用到 BoolHybridArr 这一个类其余等真有需求再碰。选型这件事永远是自己实测最靠谱。项目仓库在 GitHub / Gitee / GitCode 上都有搜 BKsell/bool-hybrid-array 就能找到。【2026-10 更新当前最新版 9.13.11】开源协议Apache-2.0 OR MulanPSL-2.0双许可注意是 MulanPSL-2.0不是 MIT也不是 MulanPSL-20。dense 侧是库自有的 uint8 位打包位图_CompactBoolArray不依赖 numpysparse 侧是 array.array(‘I’) 按每 4 字节记录异常下标没有 RLE 行程编码。同系列延伸阅读内链序列化裁决存储裁决类型裁决位运算裁决勘误文项目权威源https://pypi.org/project/bool-hybrid-array https://github.com/BKsell/bool-hybrid-array