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 进行循环并行化的主要内容,如果未能解决你的问题,请参考以下文章

如何在 python joblib 中写入共享变量

多个进程共享一个 Joblib 缓存

我们如何在与 joblib 的并行执行中使用 tqdm?

多返回值函数的joblib并行处理

使用 Joblib 将类对象实例作为输入参数的并行化函数

joblib保存模型和joblib的并行化处理和tqdm