Python - 使用 joblib 进行循环并行化
Posted
技术标签:
【中文标题】Python - 使用 joblib 进行循环并行化【英文标题】:Python - Loop parallelisation with joblib 【发布时间】:2016-09-29 11:05:00 【问题描述】:我需要一些帮助来准确了解我所做的事情/为什么我的代码没有像我预期的那样运行。
我已经开始使用 joblib 来尝试通过并行运行(大)循环来加速我的代码。
我是这样使用它的:
from joblib import Parallel, delayed
def frame(indeces, image_pad, m):
XY_Patches = np.float32(image_pad[indeces[0]:indeces[0]+m, indeces[1]:indeces[1]+m, indeces[2]])
XZ_Patches = np.float32(image_pad[indeces[0]:indeces[0]+m, indeces[1], indeces[2]:indeces[2]+m])
YZ_Patches = np.float32(image_pad[indeces[0], indeces[1]:indeces[1]+m, indeces[2]:indeces[2]+m])
return XY_Patches, XZ_Patches, YZ_Patches
def Patch_triplanar_para(image_path, patch_size):
Image, Label, indeces = Sampling(image_path)
n = (patch_size -1)/2
m = patch_size
image_pad = np.pad(Image, pad_width=n, mode='constant', constant_values = 0)
A = Parallel(n_jobs= 1)(delayed(frame)(i, image_pad, m) for i in indeces)
A = np.array(A)
Label = np.float32(Label.reshape(len(Label), 1))
R, T, Y = np.hsplit(A, 3)
return R, T, Y, Label
我一直在试验“n_jobs”,希望增加它会加快我的功能。但是,当我增加 n_jobs 时,事情会明显变慢。在没有“并行”的情况下运行此代码时,事情会变慢,直到我将作业数量从 1 增加。
为什么会这样?我知道我运行的作业越多,脚本越快?我用错了吗?
谢谢!
【问题讨论】:
首先,您运行此程序的计算机中有多少个 CPU 或内核?其次,n_jobs
设置并发运行作业的最大数量。你试过n_jobs=-1
吗?这应该使用您计算机中的所有 CPU。第三,你的 for 循环的 indeces
有多大?
我有 24 个内核和大量内存。 indeces 大约有 10,000 个条目,因此认为这将是并行化的一件好事。我可以试试 n_jobs=-1 并报告。
是的。我可以想象,如果你将 n_jobs 从 1 增加到最大值(n_jobs=23,njobs = -1),那么你将达到一个点,在这个点上,增加这个数字将涉及更多开销,因此你必须找到一个最佳点。当然,如果你可以使用 backend="threading" 可能会更好,但你必须尝试。
那么,我想推荐这篇 SO 帖子 http://***.com/questions/21027477/joblib-parallel-multiple-cpus-slower-than-single,它有很好的答案,其中一个直接来自 joblib 作者,虽然可能已经过时了......
【参考方案1】:
也许你的问题是因为image_pad
是一个大数组。在您的代码中,您使用的是默认的multiprocessing
后端joblib
。这个后端创建了一个工人池,每个工人都是一个 Python 进程。然后将函数的输入数据复制n_jobs
次并广播到池中的每个工作人员,这可能会导致严重的开销。引用joblib的文档:
默认情况下,池中的工作人员是真正的 Python 进程,当 n_jobs != 1 时,使用 Python 标准库的多处理模块派生。作为输入传递给 Parallel 调用的参数被序列化并在每个工作进程的内存中重新分配.
这对于大型参数可能会产生问题,因为它们将被工作人员重新分配 n_jobs 次。
由于这个问题经常发生在使用基于 numpy 的数据结构的科学计算中,joblib.Parallel 为大型数组提供了一种特殊处理,可以自动将它们转储到文件系统中,并将引用传递给 worker 以将它们作为该文件上的内存映射打开使用 numpy.ndarray 的 numpy.memmap 子类。这使得在所有工作进程之间共享一段数据成为可能。
注意:以下内容仅适用于默认的“多处理”后端。如果你的代码可以释放 GIL,那么使用 backend="threading" 效率更高。
所以如果是这种情况,你应该切换到线程后端,如果你能够在调用frame
时释放全局解释器锁,或者切换到joblib
的共享内存方法。
docs 表示 joblib
提供了可能有用的自动 memmap
转换。
【讨论】:
【参考方案2】:您遇到的问题很可能是 Python 编译器本质的基本问题。
如果您阅读“https://www.ibm.com/developerworks/community/blogs/jfp/entry/Python_Is_Not_C?lang=en”,您可以从一位专门优化和并行化 python 代码的专业人士那里看到,迭代大型循环对于 python 线程来说是一种固有的缓慢操作。因此,生成更多循环遍历数组的进程只会减慢速度。
但是 - 有些事情是可以做的。
Cython 和 Numba 编译器都旨在优化类似于 C/C++ 风格的代码(即您的情况) - 特别是 Numba 的新 @vectorise 装饰器允许标量函数接受并应用操作大数组与大数组以并行方式(target=Parallel
)。
我对你的代码理解不够,无法给出一个实现的例子,但是试试这个!这些编译器以正确的方式使用,在过去为我的并行进程带来了 3000,000% 的速度提升!
【讨论】:
以上是关于Python - 使用 joblib 进行循环并行化的主要内容,如果未能解决你的问题,请参考以下文章