前几天一个同事找我调内存问题:同样一份 Parquet 文件,他用 pandas 读出来的内存占用比我高一倍。我让他看了一眼 dtype,果然,他那边清一色的 int64、float64,我这边全是 int64[pyarrow]、string[pyarrow]。就这个差别,在 Pandas 2.2+ 里被很多人低估了——大家以为 PyArrow 支持只是多了一种存储格式,但真正值钱的,是它带来的内存零拷贝通路。
这篇文章想把这个话题拆透:零拷贝到底是怎么发生的、哪些操作真的零拷贝、哪些操作看似零拷贝实际在偷偷复制,以及 Copy-on-Write 和零拷贝之间到底是什么关系。适合正在做大内存数据处理、想把 pandas 从几十 G 内存里捞出来的朋友,也适合想搞明白 pandas 2.x 内部机制、避免被各种“内存优化技巧”误导的读者。
1. 先说清楚:零拷贝在Pandas里到底指什么
1.1 从Arrow列式格式讲起:为什么同样的数据,Arrow更容易共享
很多人第一反应是:numpy 切片也是 view,不复制啊,凭什么说 Arrow 能带来零拷贝?这句话对了一半。numpy 的 view 确实能在单个 ndarray 内部共享数据,但 pandas 里的数据结构远比单个 ndarray 复杂——一个 DataFrame 是多个 Block、多个索引、多个 ExtensionArray 的组合体,想让数据在整套结构里零成本流转,光靠 numpy 的 slice 语义是不够的。
Arrow 的列式 Layout 是为零拷贝而生的,典型固定宽度类型(比如 int64)的存储逻辑非常简单:一块连续的数据缓冲区 + 一个长度 + 一个 offset。第 n 个元素的地址可以原地算出来:base_ptr + (offset + n) * 8。更关键的是,Arrow 定义了一套标准的 C 数据接口,不同库之间可以直接传指针和元数据,数据本身不需要搬家。
这也解释了为什么 Parquet 文件里的数据能被 DuckDB、Polars、Arrow Flight 这些工具零拷贝共享——因为大家都认同一套内存布局协议。Pandas 2.x 接住 PyArrow 之后,等于在 pandas 和 Arrow 生态之间开了一扇门,数据可以在不复制的前提下流转。
1.2 Pandas老架构的“隐形拷贝”:BlockManager是怎么浪费内存的
老版本 pandas 的内存模型是 BlockManager:按 dtype 把若干列拼进一个二维 numpy 数组。这设计在当时是为了性能,它也确实让同 dtype 列的向量化计算很快。但它有一个隐藏代价——一旦你想取出其中一部分列,或者做某些重排操作,Block 可能被迫复制整块数据。
举个例子,df[['a', 'b']] 这种操作,如果 a、b 两个列在同一个 Block 里,为了得到一个连续的新 Block,pandas 往往得把数据从大 Block 里拷出来;而单列 df['a'] 反而可能拿到的是原生 view,于是 SettingWithCopyWarning 的坑就出现了。更麻烦的是,在 2.0 之前,pandas 根本无法精确追踪“哪些 DataFrame 共享了同一块底层数据”,为了安全,很多操作宁可复制。
这个问题不是某一段代码写得差,而是 BlockManager 的引用模型太粗。到 2.x,pandas 引入 CoW 就是为了从机制上解决这个“看不清楚共享关系”的问题,而 PyArrow dtype 则从另一边把数据内存本身变成了更适合零拷贝的形式,两者是互补的。
1.3 零拷贝不等于“马上能省内存”:它解决的是数据通路问题
我经常看到有人写“一行代码让你的 pandas 内存占用减半”,然后开一堆 PyArrow dtype。实际上 PyArrow dtype 本身并不压缩数据,int64 存进去还是 8 字节一个元素。零拷贝真正解决的,是“数据通路”上的浪费,而不是“数据本身”的缩小。
更直白地说,零拷贝解决的是这种情况:你有一份 10GB 的 Arrow 数据,想从里面切出一个子集,或者只取出其中几列。如果通路顺畅,新 DataFrame 可以继续复用那 10GB 的缓冲区,内存不会显著上涨;如果通路有问题,pandas 会先完整复制一份 10GB 的数组,内存瞬间翻倍。
但有一个边界必须说清楚:只要数据跨越了“Arrow 域”和“numpy/Python 域”之间的边界,零拷贝大概率会失效。比如把 Arrow 字符串列转成 Python object 列、把 Arrow int64 转成 numpy int64——除非布局恰好碰巧兼容,否则新建堆内存是必然的。所以后面所有实操都围绕一个原则:尽量让数据在 Arrow 域内流动,跨域转换要谨慎,最好显式控制。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
