使用Pandas chunksize读取4GB CSV仍卡顿死机,求正确处理方案
嘿,我太懂你踩的这个坑了——用pd.concat把所有chunk拼起来,本质上还是把整个4GB的CSV数据全塞进内存里了!你可别以为4GB的文件占4GB内存,CSV加载成pandas DataFrame后,内存占用往往会膨胀3-10倍(尤其是有大量字符串、自动推断的冗余数据类型时)。16GB内存看着够,但系统本身、Jupyter进程还有后台跑的其他程序都要占资源,很容易就把内存榨干,导致机器卡顿甚至死机。
下面是几个经过实践验证的靠谱解决办法,按优先级排序:
1. 别一次性拼接所有chunk,逐块处理再输出结果
如果你的任务是过滤数据、统计计算或者做转换,完全没必要把所有数据存在内存里。可以逐块处理,把每块的结果直接追加到输出文件,或者累积统计值:
# 示例1:逐块过滤后写入新CSV,全程只占一个chunk的内存 first_run = True for chunk in pd.read_csv(csv_path, chunksize=10**6): # 这里写你的过滤/处理逻辑,比如只保留value大于50的行 filtered_chunk = chunk[chunk['value'] > 50] # 第一块写入表头,后续块跳过表头 filtered_chunk.to_csv('filtered_data.csv', mode='a', header=first_run, index=False) first_run = False # 示例2:逐块计算统计值,最后合并结果 summary_stats = [] for chunk in pd.read_csv(csv_path, chunksize=10**6): chunk_stats = chunk[['col1', 'col2']].agg(['mean', 'sum']) summary_stats.append(chunk_stats) final_stats = pd.concat(summary_stats).groupby(level=0).mean()
这种方式内存占用始终控制在单个chunk的大小,绝对不会出现内存溢出的问题。
2. 只加载需要的列,砍掉不必要的内存开销
如果CSV里有很多你用不上的列,在读的时候就用usecols参数指定只加载需要的列,直接砍掉一大半内存占用:
# 假设你只需要id、category、value这三列 raw = pd.read_csv(csv_path, chunksize=10**6, usecols=['id', 'category', 'value']) # 后续再按上面的逐块处理逻辑操作
3. 提前指定紧凑的数据类型,压缩内存
pandas默认的自动类型推断经常会“浪费”内存:比如把本来能用int32存的数字推断成int64,把重复率极高的字符串列存成object类型(占内存巨多)。你可以提前定义数据类型,大幅压缩内存:
# 自定义各列的数据类型 dtype_map = { 'id': 'int32', # 比int64省一半内存 'category': 'category', # 重复率高的字符串用category,内存能省90% 'value': 'float32', # 不需要高精度的话用float32代替float64 'timestamp': 'datetime64[ns]' # 明确时间类型,避免推断出错 } raw = pd.read_csv(csv_path, chunksize=10**6, dtype=dtype_map)
4. 把CSV转成更高效的列式存储格式(适合反复读取的场景)
如果需要多次使用这个数据集,建议先把CSV转换成Parquet或者Feather格式——这两种是列式存储,支持压缩,加载速度比CSV快N倍,内存占用也远低于CSV:
# 先逐块读取CSV,转成Parquet格式存储 import pyarrow.parquet as pq import pyarrow as pa chunk_tables = [] for chunk in pd.read_csv(csv_path, chunksize=10**6): # 先优化数据类型 chunk['category'] = chunk['category'].astype('category') chunk['id'] = chunk['id'].astype('int32') # 转成pyarrow的Table对象 chunk_tables.append(pa.Table.from_pandas(chunk)) # 合并所有Table并写入Parquet文件 pq.write_table(pa.concat_tables(chunk_tables), 'processed_data.parquet') # 后续读取Parquet文件,速度快内存占用小 data = pd.read_parquet('processed_data.parquet')
Parquet还支持谓词下推,读的时候可以只加载符合条件的行/列,进一步优化性能。
5. 调整Jupyter的内存限制(治标不治本的补充方案)
如果上面的优化都做了还是有点紧张,可以调整Jupyter的内存限制。在启动Jupyter前设置环境变量:
export MEM_LIMIT=12G jupyter notebook --NotebookApp.max_buffer_size=$((12*1024*1024*1024))
不过这个只是临时缓解,核心还是要从数据处理方式上优化。
内容的提问来源于stack exchange,提问作者Hoang Pham

