使用Pool.starmap并行执行后程序额外耗时过长排查求助
并行处理后额外耗时问题分析与优化方案
问题核心原因分析
tqdm统计的是任务执行的总耗时,但脚本后续的20分钟额外开销通常来自以下几点:
- 进程池清理阶段:
with Pool块结束时会自动执行close()和join(),等待所有子进程完全终止。如果子进程持有大内存对象(比如未释放的DataFrame),操作系统回收资源的过程会显著变慢。 - 结果序列化/反序列化:
starmap需要将所有子进程的返回值统一收集后返回,这一步依赖pickle序列化。如果返回的array_split_ammo_make_version是一个极大的列表,序列化和传输数据的耗时会非常可观。 - 全局数据的子进程复制:如果
table_ecode是全局变量,在fork模式下(Linux/macOS)会采用写时复制机制,但如果函数内对select有修改操作,会触发整个DataFrame的复制,导致内存占用飙升,后续清理成本极高。
Pool.starmap是否存在异常?
starmap本身没有异常,它的设计逻辑是按顺序收集所有任务的结果,必须等最后一个子进程完成后才会返回完整结果集。这种特性在任务数量大、返回数据多的场景下,会导致tqdm进度条走完后,还需要等待结果的统一序列化和传输,这部分时间不会被tqdm统计。
更换并行化函数能否提升性能?
可以尝试,不同的并行API在结果处理方式上有差异,能针对性解决部分问题:
- 使用
imap_unordered:不保证结果顺序,子进程完成就立即返回结果,能减少内存占用(不需要缓存所有结果),同时可以边接收结果边处理(比如提前开始array_to_dict的转换),避免等待所有任务结束才处理数据。
示例代码:with Pool(mp.cpu_count()-1) as ex: results = [] for res in tqdm(ex.imap_unordered(get_best_string_filters, params), total=len(params)): results.append(res) dict_split_ammo_make_version = array_to_dict(results) - 改用
concurrent.futures.ProcessPoolExecutor:API更灵活,支持as_completed方法异步获取结果,能进一步优化结果处理的时效性,同时减少进程池管理的开销。 - 避免全局大对象传递:提前在主进程对
table_ecode按HST分组,函数内直接取分组结果,减少重复过滤和数据复制:# 主进程提前分组 grouped_table = table_ecode.groupby('HST') # 修改函数内的过滤逻辑 def get_best_string_filters(...): select = grouped_table.get_group(hst) # 替代原过滤行 # 后续逻辑不变 - 优化进程池大小:不要直接用
mp.cpu_count(),如果函数涉及IO操作(比如隐式的内存页交换),适当减少进程数(比如cpu_count()-1),避免CPU上下文切换过载。
额外优化建议
- 检查
array_to_dict和np.save的耗时:如果这两步本身就很慢,可以考虑分批处理结果并写入文件,避免一次性处理大列表。 - 替换序列化方式:用
msgpack或pyarrow替代pickle,提升数据序列化/反序列化的速度。
内容的提问来源于stack exchange,提问作者kithuto
相关产品推荐
相关产品推荐

