You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

不同Pandas DataFrame复制方式的内存消耗差异及疑问

Pandas DataFrame复制方式的内存消耗分析与常见问题解答

一、测试结果疑惑的原理解析

1. 第12行与第13行内存增量差异

第一次创建df时,除了数据本身的内存开销,还包含pandas内部初始化的额外成本(如全局索引对象、数据类型缓存、模块加载等),这部分开销仅在首次创建DataFrame时产生。第二次创建df2时,这些初始化资源已存在,因此仅新增数据相关的内存,增量与初始列表a的内存大小一致。

2. 第15行增量异常的解读

由于func2和func都被@profile装饰,memory_profiler会分别统计两个函数的内存变化。在func的统计结果中,第15行的增量显示异常是工具的统计逻辑问题:它错误地将func2函数的起始内存作为基准,而非func函数的当前内存状态。实际增量应与func2内部的增量(0.012 MiB)一致,仅对应函数调用的微小开销。

3. 深拷贝与修改未显式增加内存的原因

你的pandas版本(1.4.3)虽未默认开启写时复制(Copy-On-Write),但内部对深拷贝做了延迟优化:

  • 执行df.copy(deep=True)时,并未立即复制底层numpy数组,而是共享原数据的内存;
  • 仅当执行df6.loc[:,"val"] = 2修改数据时,才会触发实际的内存复制。但memory_profiler依赖的进程RSS统计无法精确捕捉这种延迟分配的内存,且numpy数组的整列赋值属于in-place修改,未触发新内存分配,导致增量不明显。

4. copy.deepcopy的内存异常

copy.deepcopy对DataFrame的处理本质上依赖pandas自身的深拷贝实现,因此同样受上述延迟复制优化的影响。同时,memory_profiler的RSS统计局限性,导致无法准确反映递归复制带来的内存变化。

二、不同复制方式的内存消耗差异对比

复制方式内存消耗特点数据共享情况
直接赋值(df4 = df)无额外内存消耗,仅增加对象引用完全共享所有数据与结构,修改互影响
浅拷贝(df.copy(deep=False))仅消耗元数据(索引、列名等)的微小内存共享底层数据数组,修改数据会影响原DataFrame
pandas深拷贝(df.copy(deep=True))未修改时几乎无额外内存,修改后消耗与原DataFrame数据量相等的内存未修改时共享数据,修改后独立拥有数据
copy.deepcopy(df)理论上消耗与原DataFrame完全相等的内存,实际受pandas优化影响,未修改时延迟分配递归复制所有对象,最终数据完全独立

三、内存消耗研究的替代方法

1. pandas自带memory_usage()

直接计算单个DataFrame的真实内存消耗,支持深度统计对象类型数据:

import pandas as pd

df = pd.DataFrame({"val": [1]*(10**6)})
# 统计包括底层数据在内的总内存
total_mem = df.memory_usage(deep=True).sum() / 1024 / 1024
print(f"DataFrame总内存:{total_mem:.2f} MiB")

2. Python标准库tracemalloc

精确跟踪内存分配的细节,对比不同操作的内存变化:

import tracemalloc
import pandas as pd
from copy import deepcopy

tracemalloc.start()

# 初始状态
a = [1]*(10**6)
df = pd.DataFrame({"val": a})
snapshot_init = tracemalloc.take_snapshot()

# 深拷贝
df_deep = df.copy(deep=True)
snapshot_deep = tracemalloc.take_snapshot()

# 修改深拷贝后的DataFrame
df_deep.loc[:, "val"] = 2
snapshot_modify = tracemalloc.take_snapshot()

# 输出内存变化
print("深拷贝后的内存差异:")
for stat in snapshot_deep.compare_to(snapshot_init, 'lineno')[:5]:
    print(stat)

print("\n修改后的内存差异:")
for stat in snapshot_modify.compare_to(snapshot_deep, 'lineno')[:5]:
    print(stat)

3. sys.getsizeof结合numpy数组nbytes

手动计算DataFrame的总内存(包含底层数组):

import sys
import pandas as pd

df = pd.DataFrame({"val": [1]*(10**6)})
# DataFrame对象本身的内存 + 所有列数组的内存
total_mem = sys.getsizeof(df) + sum(col.values.nbytes for col in df.columns)
print(f"总内存:{total_mem / 1024 / 1024:.2f} MiB")

4. pympler库的asizeof

递归计算对象的总内存,包含所有嵌套对象:

from pympler import asizeof
import pandas as pd

df = pd.DataFrame({"val": [1]*(10**6)})
print(f"总内存:{asizeof.asizeof(df) / 1024 / 1024:.2f} MiB")

四、关于psutil统计的补充说明

memory_profiler依赖psutil获取进程RSS(常驻集大小),而RSS包含操作系统缓存的内存,无法精确反映Python对象的实际内存占用。在OS X和Ubuntu上的一致结果并非psutil的bug,而是RSS统计本身的局限性——它无法区分共享内存、延迟分配内存与实际对象占用的内存。

内容的提问来源于stack exchange,提问作者jjei

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 08:15:30