news 2026/8/5 17:11:31

我踩的那些 Python 布尔数组的坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
我踩的那些 Python 布尔数组的坑

那天我在做日志埋点数据的去重统计,每个用户当天有没有触发过某个事件,有就是 True,没有就是 False。数据量不大,大概几百万行日志,我觉得随便写写就行了。

上线那天下午,运维突然打电话说机器内存炸了。我说不可能啊,我代码写得挺简单的啊。运维在电话那头沉默了两秒,然后幽幽地来了一句"你自己看吧",那语气我到现在都记得,像极了班主任发现你作业是抄的。

我当时写的就是个普普通通的 list,装了一堆 True 和 False。打开终端一看,进程被 kill 了,理由是 OOM。

flags=[False]*50_000_000# 一个 True/False 在 Python 里是完整对象,约 28 字节# 五千万个就是 1.4GB,还没开始算就 OOM 了

我这才意识到,一个 True 在 Python 里可不是一个 bit,它是个完整的 Python 对象,背后有一整套引用计数和类型信息这些东西,一个布尔值吃掉了差不多 28 个字节。几千万个 28 字节,堆在一起直接就翻车了。你说气不气,我明明只存了个"是"和"否",Python 非要给我配一整套"户口本"。那一刻我脑子里就一句话:这波血亏。我甚至怀疑 Python 是不是觉得我存的是个"有身份的布尔值",得给它上户口、办社保、交公积金。

后来我换成了 array,以为 stdlib 的东西总归靠谱。写的时候还挺高兴,内存确实降下来了,几千万条记录也能跑。我当时还跟同事吹牛,说你看,标准库就是稳,一个字节一个布尔,这不就解决了嘛。

fromarrayimportarray flags=array('b',[0])*50_000_000# 每个元素只占 1 字节,内存确实下来了# 但业务线一扩,长度一变,它又开始吃紧

结果改需求了,老板说要扩充到好几个业务线,数据量大了一截,array 又倒了。我心里窝火得很,感觉每个方案都只差那么一口气,就像打游戏差最后一刀就能通关,结果 boss 狂暴了。这波啊,这波是"我裂开了"。我那天晚上回家路上还在想,是不是我写代码的姿势不对,要不要去庙里拜拜。

然后我想到 numpy。写数据处理嘛,迟早得用它的。用了半年没事,直到有一次上游来了一个亿的数据,直接把进程干掉了。numpy 的 bool_ 虽然是比 list 省多了,但一个 bool 也要一个字节,一个亿就是一百兆左右,再加上副本、临时变量、广播计算,实际跑起来内存经常爆表。

importnumpyasnp flags=np.zeros(100_000_000,dtype=bool)# bool_ 一个占 1 字节,一亿就是 100MB# 加上副本和广播,内存直接爆表

那种报错框跳出来的时候我就坐在椅子上发呆,心想一个亿就炸了,后面还有十亿的量等着呢。这感觉就像你刚换了个大点的钱包,结果工资还没捂热,账单先来了。我当时只想对 numpy 说一句:你礼貌吗?我甚至能脑补出 numpy 的内心独白:你要的是省内存,我可没说我不吃内存啊。

我这时候有点急了,开始翻各种文档。找到 bitarray 的时候觉得看到了光,一个 bit 存一个布尔值,这才是正解啊。一亿个布尔值才十几兆,内存瞬间就安全了。我当时激动得差点在工位上喊出来,同事以为我中了彩票。

frombitarrayimportbitarray flags=bitarray(1_000_000_000)# 一个 bit 存一个布尔值,内存确实省# 但十亿个 bit 搬进 CPU 的那条路,成了新的瓶颈

我高高兴兴换成 bitarray 跑了两天,然后数据量又涨了,过了十亿之后它也开始扛不住。不是内存不够,而是操作变得很慢。我后来才明白,十几兆看着小,但数据量大了以后从内存往 CPU 搬数据这件事本身就变成了瓶颈,内存占用多就会卡,这就是所谓的内存墙,普通人直观感受就是电脑风扇狂转、进度条凝固罢了。就像你搬家,东西是少了,但路就那么宽,一趟趟搬反而更慢。这大概就是传说中的"路虽近,堵车"。我那时候盯着风扇狂转的电脑,感觉它不是在跑程序,是在跟我抗议。

也不是没人推荐我用 scipy.sparse。我试了,文档写得是真晦涩,sparse.csr_matrix 把布尔数据转成稀疏矩阵处理。写起来还行,查那个 nonzero 的速度也挺利索。但在我的场景里密度不固定,有时候稀疏部分突然变密,它就僵在那儿转圈了。

fromscipy.sparseimportcsr_matrix flags=csr_matrix((1,100_000_000),dtype=bool)# 稀疏矩阵适合数学场景,密度一上来就僵住# 函数调用开销大,千万次叠加就卡

sparse 的初衷是处理数学上的稀疏矩阵,跟我的布尔序列批量标记匹配不是一回事,函数调一次开销大,千万次叠加起来就变成肉眼可见的卡顿。这就像你拿了个专业单反去拍大头贴,功能是强大,但快门按下去那一下,够你喝杯茶了。我只能说,这波是"杀鸡用牛刀,牛刀还卡壳"。我甚至怀疑 scipy 是不是觉得我这种用法是在侮辱它。

把 scipy.sparse 删掉那天下午,我随手在 GitHub 上瞎搜,纯粹是碰运气。翻了好几个不相关的项目,最后撞上一个叫 bool-hybrid-array 的库,星星少得可怜,但因为名字里有 hybrid 我就点进去了。我当时想的是,反正都这样了,看看也不亏。说实话,第一眼看到那个 star 数,我心里是打鼓的,毕竟谁都不想在生产环境里赌一个随时可能没人维护的库。但翻完 README 和源码,我反而踏实了,它底层就是 numpy 和 array 这两个我天天在用的东西,没有自己造轮子,没有黑魔法,核心逻辑就几百行,我甚至能一行行看懂它在干嘛。这种库反而让我放心,因为它不依赖什么玄学,就是老老实实把两个成熟方案拼在一起,出问题我也能自己进去改。

看了两眼它的逻辑我就傻了。它居然把数组切成两块,一块是密集区,用 numpy.ndarray 存,因为多数是连续 True 或连续 False,这部分操作快;另一块是稀疏区,用 array.array 存,因为是零散的异常值,只记录索引就够了。它不是猜,它是自动根据内容判断该走哪条路。我拿自己那个快把我逼疯的数据塞进去跑,内存从过去的百兆级别直接掉到了十几兆,手指点在回车上的瞬间已经返回结果了。那一刻我差点从椅子上跳起来,感觉像在垃圾堆里捡到了宝。这波啊,这波是"真香"。我甚至想给这个库的作者发个红包,虽然我不认识他。后来我又仔细看了下它的依赖,就 numpy 和 array,都是 Python 生态里最老牌、最稳的东西,它自己只是薄薄一层封装,没有引入任何冷门依赖。这意味着就算这个库哪天不更新了,只要 numpy 和 Python 还在,它就能一直跑,我甚至可以直接把它的核心逻辑抄进自己项目里,反正就几百行。这种"底层全是熟脸"的库,用起来是真的安心。

我后来专门试了一下修改长度的情况。很多方案一开始内存小跑得快,但只要长度一改,比如追加或删除一段,内存立刻就掀翻。list 的扩容策略会预留空间,array 也一样,numpy 更狠,改长度意味着整块重新申请内存,一亿规模的数组 resize 一下,程序直接僵住。我试过在 numpy 上动态追加数据,每次 append 都像在开盲盒,不知道哪次就内存错误了。这个方案的好处是它两面都有,密集部分用 numpy,但那部分长度是不怎么变的,稀疏部分用 array,长度可变但没有重新申请整块的压力。长度变化时的内存墙,在它这儿不算是绝症,因为它把变量部分放在了负担最轻的地方。我后来想想,这就像搬家的时候把重的东西放固定位置,轻的东西随便挪,反而最省力。而且它没有搞什么花里胡哨的 C 扩展或者编译步骤,pip 装完就能用,跟装 numpy 一样省心,不会出现那种装个库还要配半天编译环境的糟心事。对我来说,一个库稳不稳,就看两件事,一是底层是不是熟脸,二是装起来麻不麻烦,这两点它都过关了。

我现在回想起那些半夜对着 top 命令发呆的日子,就觉得好笑。最开始只是一个 list,后来 array、numpy、bitarray、scipy.sparse,一个接一个地试,每次都是换个场景换个死法。直到翻到一个没几个人 star 的库,才把这条路走通。有时候真觉得,踩坑这事儿,踩得多了,坑都认识你了。这大概就是传说中的"菜是原罪",但菜着菜着,也就熟了。我现在看到内存相关的报错,第一反应不是慌,而是先笑一声,然后默默打开 GitHub 开始搜。

不过说真的,我一开始也担心过这个库会不会哪天就没人管了。毕竟 star 数摆在那儿,谁看了都得犹豫一下。后来我实在憋不住,直接去 GitHub 上给作者发了封邮件,问他这库还维护不维护。结果他回得挺快,说只要他没事就不会停止维护,有时候忙起来可能几个月没动静,但一闲下来就疯狂更新,跟打了鸡血似的。我顺手翻了翻版本历史,好家伙,真不是吹的,隔三差五就一个 commit,有时候一天好几个,那更新频率比我写日报还勤。看到这儿我彻底放心了,这哪是没人管的弃坑项目,分明是作者拿它当亲儿子在养。而且它底层就依赖 numpy 和 array 这两个 Python 生态里最老牌、最稳的库,没有引入任何冷门依赖,没有自己造轮子,没有黑魔法。这意味着就算哪天它真不更新了,只要 numpy 和 Python 还在,它就能一直跑。我甚至可以直接把它的核心逻辑抄进自己项目里,反正代码就摆在那儿,出问题我也能自己进去改。这种"底层全是熟脸"的库,用起来是真的安心,比那些动不动就拉一堆依赖、装个库还要配半天编译环境的家伙靠谱多了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/5 17:11:30

GitHub Desktop终极汉化指南:3分钟免费打造中文版Git客户端

GitHub Desktop终极汉化指南:3分钟免费打造中文版Git客户端 【免费下载链接】GitHubDesktop2Chinese GithubDesktop语言本地化(汉化)工具 【GitHub桌面客户端中文汉化】 项目地址: https://gitcode.com/gh_mirrors/gi/GitHubDesktop2Chinese 你是否因为GitHu…

作者头像 李华
网站建设 2026/8/5 17:08:46

STM32驱动WS2812B灯带:SPI/PWM/DMA方案详解与避坑指南

1. 从“点灯”到“调色”:WS2812B驱动进阶的核心挑战 上一篇文章我们聊了怎么用STM32最基本的GPIO翻转去“点亮”一颗WS2812B-2020灯珠,算是成功迈出了第一步。但玩过这类可编程灯带的朋友都知道,点亮只是开始,真正的乐趣在于“控…

作者头像 李华
网站建设 2026/8/5 17:08:38

Visual Studio 2022安装与WPF上位机开发环境配置指南

1. Visual Studio 2022安装概述 作为微软最新的集成开发环境,Visual Studio 2022(简称VS2022)在WPF上位机开发领域有着不可替代的地位。我使用VS进行工业控制上位机开发已有8年经验,从早期的VS2010到现在的VS2022,见证…

作者头像 李华
网站建设 2026/8/5 17:04:17

营销数据是什么?从零理解企业营销数字化的核心资产

【摘要】营销数据是什么?它是记录企业获客全流程——从曝光、点击、转化到复购——的数字化资产,帮企业看清楚了"钱花在哪、客户从哪来、哪个渠道最值钱"这三个核心问题。 "一个做线下连锁做了十年的老板,去年开始认真做线上…

作者头像 李华
网站建设 2026/8/5 17:03:35

2026年PDF拆分合并工具盘点:7款实测后哪些真的省事又无水印

上个月整理一摞厚厚的设备采购发票,财务要求把每家的合同、验收单和发票扫描件按供应商顺序拼成一份完整的报销材料,中间还要抽掉几页内部流转单。文件多、页数杂,光靠打印店的扫描仪一张张导出来再手动拖拽,一个下午就搭进去了。…

作者头像 李华