升级至Python3.6后joblib并行计算无法缩短耗时问题
解决Python 3.6升级后joblib并行计算不提速的问题
我之前也碰到过类似的情况,从Python 3.5升到3.6后,用joblib跑并行反而比串行还慢,结合你给出的环境和代码,咱们来拆解问题和解决办法:
先把你的复现代码补全(方便测试)
首先我把你给出的代码补全成可运行的版本,这样能直观看到耗时差异:
import time import numpy as np from joblib import Parallel, delayed def square_int(i): return i * i ndata = 1000000 # 串行计算 ti = time.time() results = [] for i in range(ndata): results.append(square_int(i)) duration_serial = time.time() - ti print(f"串行耗时: {duration_serial:.2f}秒") # 并行计算 ti = time.time() results_parallel = Parallel(n_jobs=-1)(delayed(square_int)(i) for i in range(ndata)) duration_parallel = time.time() - ti print(f"并行耗时: {duration_parallel:.2f}秒")
问题根源分析
为什么升级后并行没效果?主要有这几个原因:
- 微任务的进程开销远超收益:你的
square_int是个极其轻量的函数,多进程启动、进程间数据传递的固定开销,比并行计算节省的时间还多,自然会变慢。 - Python 3.6的进程启动策略变化:在macOS上,Python 3.6开始默认把多进程启动方式从
fork改成了spawn。spawn需要重新加载整个模块、初始化环境,启动开销比fork大很多,进一步放大了微任务的并行劣势。 - 旧版joblib的适配问题:你用的joblib 0.11是比较老的版本(现在最新是1.x系列),对Python 3.6的新特性适配不够完善,调度效率不如新版本。
针对性解决方案
方案1:合并小任务,分摊进程开销
把大量微任务打包成少数大任务,让每个进程处理足够多的计算,就能体现并行的优势:
def square_batch(batch): return [i*i for i in batch] # 拆分数据为批次,每个批次处理10000个任务 batch_size = 10000 batches = [range(i, min(i+batch_size, ndata)) for i in range(0, ndata, batch_size)] ti = time.time() results_parallel = Parallel(n_jobs=-1)(delayed(square_batch)(batch) for batch in batches) # 合并批次结果 results_parallel = [item for sublist in results_parallel for item in sublist] duration_parallel = time.time() - ti print(f"批量并行耗时: {duration_parallel:.2f}秒")
方案2:切换回fork启动策略(仅Unix/Linux/macOS可用)
如果你的系统支持fork,可以强制joblib用这个启动方式,大幅降低进程启动开销:
from joblib import parallel_backend with parallel_backend('loky', n_jobs=-1, prefer='fork'): ti = time.time() results_parallel = Parallel()(delayed(square_int)(i) for i in range(ndata)) duration_parallel = time.time() - ti print(f"fork模式并行耗时: {duration_parallel:.2f}秒")
注意:macOS上如果是GUI程序,fork可能会有兼容性问题,但纯计算场景完全没问题。
方案3:升级joblib到最新版本
旧版joblib对Python 3.6的支持不够到位,升级后能获得更好的调度逻辑和性能:
pip install --upgrade joblib
额外惊喜:用numpy向量化替代循环(最适合你的场景)
对于这种简单的数值计算,numpy的向量化操作比串行、并行都快得多,完全不需要多进程:
ti = time.time() arr = np.arange(ndata) results_numpy = arr * arr duration_numpy = time.time() - ti print(f"numpy向量化耗时: {duration_numpy:.2f}秒")
内容的提问来源于stack exchange,提问作者phypho
相关产品推荐
相关产品推荐

