去年有回跑一个图像分类实验,模型训练到第80个epoch,宿舍断电前一切正常。第二天重启脚本,内存从8GB慢慢爬到32GB,最后直接被系统kill。一周的数据预处理全白做。
这种场景做科研的人都不陌生:读大CSV、批量处理图片、循环生成中间结果,内存越跑越高。重启能好一会儿,但治标不治本。问题往往不是Python本身,而是代码里某个地方在偷偷吃内存。
今天分享我排查内存问题的3个工具,从"确认元凶"到"定位代码行"再到"找到具体对象",全程不用花钱。
先用htop确认:是不是Python在吃内存
很多科研人第一步就错了——看到电脑卡,直接怀疑Python,然后开始乱改代码。其实先要用系统工具确认:到底是哪个进程在消耗内存。
工具:htop(Linux/macOS)或Windows任务管理器。
打开终端输入htop,按M键按内存排序。如果你的Python进程内存曲线持续上升,说明问题在代码里。如果是其他进程(比如浏览器开了50个标签),那跟Python没关系。
关键观察点:
- RES列:进程实际占用的物理内存,这个数如果一直涨,就是真泄漏。
- 曲线是否稳定:训练模型时内存有波动正常,但完成一个epoch后应该回落或稳定。如果每个epoch都比上一个高,说明有累积。
这一步只用10秒钟,但能避免你后面白忙一场。
再用memory_profiler:定位到具体代码行
确认是Python的问题后,下一步是找到哪一行代码在吃内存。这时候用memory_profiler。
安装:
pip install memory_profiler
用法:在要分析的函数前加@profile装饰器,然后运行:
python -m memory_profiler your_script.py
输出类似这样:
Line # Mem usage Increment Line Contents =============================================== 12 85.2 MiB 85.2 MiB @profile 13 def process_data(): 14 105.4 MiB 20.2 MiB df = pd.read_csv("data.csv") 15 305.4 MiB 200.0 MiB features = df.apply(complex_transform, axis=1) 16 505.4 MiB 200.0 MiB result = features.tolist()
每一行的增量(Increment)清清楚楚。上面的例子中,真正的问题是features.tolist()和df.apply()各涨了200MB。如果你原本怀疑是read_csv,这里就能看出不是它。
科研场景里最常见的内存大户:
pd.read_csv().tolist()- matplotlib画图时累积figure对象,没调用
plt.close()。
最后用tracemalloc:找到泄漏的具体对象
有些问题更隐蔽:内存不是在某一行突然爆涨,而是慢慢累积。比如循环里不断创建缓存、回调函数持有引用、全局列表无限增长。这时候要用Python内置的tracemalloc。
它是Python标准库,不用安装。
import tracemalloc tracemalloc.start() # 运行你的代码 for epoch in range(10): train_one_epoch() # 每个epoch后快照对比 snapshot = tracemalloc.take_snapshot() top_stats = snapshot.statistics('lineno')[:5] print(f" === Epoch {epoch} 内存增长TOP5 ===") for stat in top_stats: print(stat)
输出会告诉你:哪个文件、哪一行、分配了多少内存、增长了多少。比如:
<frozen importlib._bootstrap>:228: size=4855 KiB (+4855 KiB), count=39332 (+39332) my_script.py:42: size=2048 KiB (+1024 KiB), count=256 (+128)
第二行显示my_script.py第42行,这个epoch比上一个多了1024KB。连续几个epoch都涨,基本就是泄漏点。
3个最容易踩的坑
- 坑1:循环里累积结果。
results = []然后每个epoch都results.append(big_array),最后内存自然爆炸。解决:只保留必要指标,大数据写到磁盘。 - 坑2:DataFrame的隐形拷贝。
df[df.label == 1]会返回新DataFrame,如果循环里反复做,内存翻倍。解决:用inplace=True或先取索引再批量操作。 - 坑3:没关的figure。matplotlib每画一张图就创建一个figure对象,循环里不
plt.close(),内存会被吃光。解决:画图后立刻关闭,或用plt.clf()。
免费还是付费?
这3个工具全是免费的:
htopmemory_profilertracemalloc
不需要买任何商业性能分析工具,科研场景完全够用。
关注后回复【内存排查】,领取:
你遇到过最离谱的内存爆炸场景是什么?是CSV太大、循环没清缓存,还是画图没关figure?评论区说说,点赞最高的我整理成一期"内存陷阱大全"。
这是「科研人效率工具系列」第14期,上期:论文Introduction写到凌晨3点?这个4段式模板让我2小时写完开头。下期预告:AI辅助文献综述:30篇论文1小时理清研究领域脉络。
— 关注「科研创新社」,每天一个科研效率提升技巧 —