本节摘要:合并 SOURCE 5.1 内存/CPU 与 5.2 大数据策略要点:downcast、del/gc、生成器、numpy 向量化、multiprocessing、Dask、分块 read_csv。远程云计算(原 5.3)简述为「数据不出境则本地/Hub 优先」。
阅读完本节,你应当能够:
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 示例)。
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) 聚合后再画图。
SOURCE 对比循环 vs numpy 平方:
import numpy as np numbers = list(range(10000)) # 循环 vs np.array(numbers)**2 —— 用 %timeit 量
multiprocessing 适合 CPU 密集且可并行任务(SOURCE 给出 Pool 示例思路);I/O 密集用多线程或 Dask。
| 手段 | 场景 |
|---|---|
| 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 时间。
下一节 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。
Python 的引用计数在变量重绑定时自动回收,但 Notebook 会话里大 DataFrame 的引用可能残留在别处(比如 df_copy = df 之后)。跑完大分析后执行 del big_df; gc.collect(),配合 5.2 节 ExecuteTime 看内存是否下降。注意:del 之后再引用该变量会 NameError,确认后续不再用才删。
生成器省内存但不随机访问:next() 只能顺序取,没有 len、不能索引。适合流式处理"遍历一次即可"的场景,比如分块聚合、逐行写日志。需要多次随机取值的中间结果仍应保留列表,别为了省内存写出取不到数据的生成器。
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 或环境说明,别人复现你的性能结论时才知道要在同一条件下比较,避免"我这边明明更快"的无谓争论。