Python - 使用joblib进行循环并行化

12

我希望能够得到帮助,了解我所做的事情以及为什么我的代码没有按照预期运行。

我开始使用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的增加,事情变得相当缓慢。当不使用“Parallel”运行此代码时,速度较慢,直到将作业数量从1增加。

为什么会这样?我以为运行的作业越多,脚本就会越快?我使用方法错误吗?

谢谢!


首先,您的计算机有多少个CPU或核心? 其次,“n_jobs”设置同时运行作业的最大数量。您尝试过“n_jobs=-1”吗?这应该使用计算机中的所有CPU。 第三,您的for循环中的“indices”有多大? - fedepad
我有24个核心和大量的内存。索引大约有10,000个条目,所以我认为这将是一个很好的并行处理的事情。我可以尝试n_jobs=-1并回报结果。 - JB1
是的。我可以想象,如果你将n_jobs从1增加到最大值(n_jobs=23,n_jobs=-1),那么你会达到一个点,在这个点上增加这个数字将涉及更多的开销,因此你必须找到一个平衡点。当然,如果你可以使用backend="threading"可能会更好,但你必须进行实验。 - fedepad
然后,我想建议这个SO帖子,https://dev59.com/zGEi5IYBdhLWcg3wseGA它有非常好的答案,其中一个直接来自joblib作者,尽管可能已经过时... - fedepad
2个回答

3
也许您的问题是因为image_pad是一个大数组。在您的代码中,您正在使用joblib的默认multiprocessing后端。此后端创建了一个工作进程池,每个进程都是Python进程。然后将函数的输入数据复制n_jobs次,并向池中的每个工作程序广播它们,这可能会导致严重的开销。引用joblib文档中的内容:

默认情况下,当n_jobs != 1时,池的工作进程是使用Python标准库的multiprocessing模块派生的实际Python进程。传递给Parallel调用的参数会被序列化并重新分配到每个工作进程的内存中。

对于大型参数,这可能会有问题,因为它们将被工作进程重新分配n_jobs次。

由于这个问题经常在基于numpy的数据结构的科学计算中出现,joblib.Parallel提供了一种特殊处理方式来自动将它们转储到文件系统并传递一个引用给工作线程,在该文件上使用numpy.memmap numpy.ndarray的子类以内存映射的方式打开它们。这使得可以在所有工作进程之间共享数据段。

注意:以下内容仅适用于默认的“multiprocessing”后端。如果您的代码可以释放GIL,则使用backend=“threading”更有效。

如果这是您的情况,您应该切换到线程后端,如果调用frame时能够释放全局解释器锁,或者切换到joblib的共享内存方法。
文档说明joblib提供了一种自动memmap转换,这可能会很有用。

3

很可能你遇到的问题是与Python编译器本质相关的基础问题。

如果你阅读https://www.ibm.com/developerworks/community/blogs/jfp/entry/Python_Is_Not_C?lang=en,你会看到一位专门从事优化和并行化Python代码的专业人士指出,对大型循环进行迭代是Python线程执行的固有缓慢操作。因此,生成更多循环数组的进程只会使事情变得更慢。

然而,还是有一些可以做的事情。

CythonNumba编译器都旨在优化类似C/C++风格的代码(即你的情况)- 特别是Numba的新@vectorise装饰器允许标量函数以并行方式(target=Parallel)接收和应用大型数组上的操作。

我不太理解你的代码,无法给出实现示例,但是可以尝试这样做!过去,正确使用这些编译器对于并行处理带来了3000,000%的速度提升!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接