使用joblib与skimage多进程遇PicklingError:无法序列化任务至工作进程
解决栅格成本面并行化最小成本路径的PicklingError问题
问题根源
MCP_Geometric实例是基于C扩展的复杂对象,无法被Python的pickle序列化,而joblib在跨进程传递数据时依赖序列化机制,因此抛出PicklingError。
解决方案
方案1:在子进程中独立初始化MCP_Geometric
成本面是可序列化的numpy数组,让每个任务自行创建MCP实例,避免传递已初始化的复杂对象,这是最稳妥的解决方式:
import numpy as np from skimage.graph import MCP_Geometric from joblib import Parallel, delayed np.random.seed(123) cost_surface = np.random.rand(1000, 1000) pois = [(np.random.randint(0, 1000), np.random.randint(0, 1000)) for _ in range(20)] def task(poi, cost_surface, pois): # 每个子进程单独创建MCP实例 mcp = MCP_Geometric(cost_surface) costs_array, traceback = mcp.find_costs(starts=[poi], ends=pois) ends_idx = tuple(np.asarray(pois).T.tolist()) costs = costs_array[ends_idx] tracebacks = [mcp.traceback(end) for end in pois] return costs, tracebacks # 返回计算结果 # 并行执行,传递成本面和POI列表 results = Parallel(n_jobs=6)(delayed(task)(poi, cost_surface, pois) for poi in pois)
方案2:开启内存共享优化
joblib默认使用loky后端,对numpy数组支持内存共享,避免重复拷贝大数组,能进一步提升性能:
results = Parallel(n_jobs=6, backend='loky', mmap_mode='r')( delayed(task)(poi, cost_surface, pois) for poi in pois )
mmap_mode='r'让所有子进程共享成本面数组的内存空间,减少内存占用和数据拷贝开销。
方案3:避免传递大POI列表(可选优化)
如果POI列表规模极大,可将其设为全局变量,利用进程fork机制继承(仅适用于Linux/macOS):
import numpy as np from skimage.graph import MCP_Geometric from joblib import Parallel, delayed np.random.seed(123) cost_surface = np.random.rand(1000, 1000) pois = [(np.random.randint(0, 1000), np.random.randint(0, 1000)) for _ in range(20)] def task(poi): mcp = MCP_Geometric(cost_surface) costs_array, traceback = mcp.find_costs(starts=[poi], ends=pois) ends_idx = tuple(np.asarray(pois).T.tolist()) costs = costs_array[ends_idx] tracebacks = [mcp.traceback(end) for end in pois] return costs, tracebacks results = Parallel(n_jobs=6)(delayed(task)(poi) for poi in pois)
注:Windows系统默认用spawn模式,全局变量不会被继承,此方案不适用。
关键说明
- 子进程初始化MCP的开销远小于串行执行的时间损耗,完全可接受。
- numpy数组原生支持序列化,传递成本面不会有问题。
内容的提问来源于stack exchange,提问作者Carla S.
相关产品推荐
相关产品推荐

