5.1 性能与资源管理


5.1 性能与资源管理

本节摘要:合并 SOURCE 5.1 内存/CPU 与 5.2 大数据策略要点:downcast、del/gc、生成器、numpy 向量化、multiprocessing、Dask、分块 read_csv。远程云计算(原 5.3)简述为「数据不出境则本地/Hub 优先」。

本节地图

阅读完本节,你应当能够:

  1. 用 pd.to_numeric downcast 与 int8/int16 降低 DataFrame 内存
  2. 用 del 与 gc.collect() 释放大对象
  3. 用生成器替代巨型列表
  4. 用 %timeit 证明向量化优于 Python 循环
  5. 对大 CSV 使用 chunksize 或 Dask

一、内存:downcast 与删除

SOURCE 5.1.1 示例:

import pandas as pd import numpy as np df = pd.DataFrame({"col1": [1,2,3,4,5], "col2": [100,200,300,400,500]}) df["col1"] = pd.to_numeric(df["col1"], downcast="integer") df["col2"] = pd.to_numeric(df["col2"], downcast="integer") print(df.memory_usage(deep=True).sum())

进一步 astype(np.int8) 等同理。del large_array + gc.collect() 在跑完大中间结果后手动释放(SOURCE 示例)。

二、生成器 vs 列表

SOURCE:

numbers_list = [i for i in range(1000000)] # 占满内存 numbers_generator = (i for i in range(1000000)) # 按需产生

Notebook 里 EDA 不必 materialize 全表——for chunk in pd.read_csv(..., chunksize=100000) 聚合后再画图。

三、CPU:向量化与并行

SOURCE 对比循环 vs numpy 平方:

import numpy as np numbers = list(range(10000)) # 循环 vs np.array(numbers)**2 —— 用 %timeit 量

multiprocessing 适合 CPU 密集且可并行任务(SOURCE 给出 Pool 示例思路);I/O 密集用多线程或 Dask。

四、大数据策略(SOURCE 5.2 并入)

手段 场景
Dask DataFrame 超内存表
read_csv chunksize 分块聚合
列裁剪 usecols 只读必要列
category dtype 低基数文本

远程 GPU/云计算(SOURCE 5.3):敏感数据优先内网 JupyterHub;Colab 仅非机密试验。

内存优化优先级

内存优化优先级

⚠️ 常见坑:multiprocessing 在 Windows Notebook 需 if __name__ == "__main__" 保护——否则 spawn 死循环。

💡 关键直觉:先 %timeit 找热点,再决定 downcast 还是 Dask——别过早微优化 import 时间。

一节小结

  • downcast / int8:减内存
  • del + gc.collect():释放大对象
  • 生成器 / chunksize:流式处理
  • numpy 向量化 + %timeit
  • Dask:超内存表

下一节 5.2 扩展与高级功能 用 nbextensions 提界面效率。

内存分析工作流

# 1. 先量化:哪列最占内存 mem = df.memory_usage(deep=True) print(mem.sort_values(ascending=False).head(10)) # 2. downcast 数值列 for col in df.select_dtypes(include="number").columns: df[col] = pd.to_numeric(df[col], downcast="integer") # 3. 低基数文本转 category for col in df.select_dtypes(include="object").columns: if df[col].nunique() / len(df) < 0.1: df[col] = df[col].astype("category")

顺序很重要:先看 memory_usage 找到占内存的列,再针对性优化,而不是全表无差别 downcast。

什么时候值得手动 del

Python 的引用计数在变量重绑定时自动回收,但 Notebook 会话里大 DataFrame 的引用可能残留在别处(比如 df_copy = df 之后)。跑完大分析后执行 del big_df; gc.collect(),配合 5.2 节 ExecuteTime 看内存是否下降。注意:del 之后再引用该变量会 NameError,确认后续不再用才删。

生成器的使用边界

生成器省内存但不随机访问:next() 只能顺序取,没有 len、不能索引。适合流式处理"遍历一次即可"的场景,比如分块聚合、逐行写日志。需要多次随机取值的中间结果仍应保留列表,别为了省内存写出取不到数据的生成器。

CPU 密集任务的并行提示

  • 纯数值计算:numpy 向量化优先,Multiprocessing 是次选。
  • I/O 密集:多线程 / Dask,避免把大量时间花在等待磁盘上。
  • Windows 下 multiprocessing 必须 if __name__ == "__main__": 保护,否则进程反复 fork 导致卡死。

内存泄漏与引用残留

Notebook 里"内存没降下来"最常见的原因不是 Python 泄漏,而是变量引用还在:例如 df2 = df 后 del df,df2 仍持有全部数据。排查时先 %whos 看还有哪些大对象,确认没有再 gc.collect()。

%whos DataFrame # 只看 DataFrame 类型 del df_temp import gc; gc.collect()

什么时候不值得优化

%timeit 之前的黄金法则是"先测再优化"。一段只跑一次、耗时几秒的探索代码,不值得为了省 20% 时间引入复杂度。把优化留给"反复跑"的代码:循环里的重复计算、每次 Run All 都执行的大转换、提交前必跑的长 pipeline。

性能优化的记录习惯

每次优化前用 %timeit 记录基线数字,优化后再测一次,把前后对比写进 Markdown 格。数据量、机器配置、内核版本会影响绝对数字,但同一环境下前后对比能真实反映优化效果。这份记录既是自查,也是向同事证明"为什么值得这么做"的依据。

资源管理的三条底线

第一,大对象用完即删,别等内存报警;第二,能用向量化绝不用 Python 循环,先把 numpy 当作第一选择;第三,超内存数据及时上 chunksize 或 Dask,不要拿整机内存赌一次成功。守住这三条,绝大多数性能问题在出现前就被拦住了。

从分析需求反推资源策略

选内存优化还是分布式方案,取决于你的分析频率和一次性还是长期。一次性探索大表用 chunksize 临时顶住就够了;每周都要跑的分析,值得花半天把清洗下沉到 Dask 或预聚合。判断标准只有一个:这项数据以后会不会反复处理。

性能优化的文档化

把每次优化前后对比(%timeit 数字、内存占用)记进 Notebook 末尾,形成一份"性能优化记录"。三个月后回头能看清哪些优化值得保留、哪些被数据规模变化抵消,也为同事提供了一份可复用的经验。

用数据说话而不是猜

怀疑某段代码慢时,不要靠感觉判断,直接用 %timeit 测两个候选写法,让数字决定取舍。同样的道理适用于内存:先 memory_usage 找出大头,再决定优化方向。整个资源管理章节的方法论可以浓缩为一句:先量化,再动手,优化前后都要有数字对比。

复现环境的最后一环

性能优化往往依赖具体版本(pandas、numpy 的算法差异明显)。把关键版本号写进 requirements.txt 或环境说明,别人复现你的性能结论时才知道要在同一条件下比较,避免"我这边明明更快"的无谓争论。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U