在Python中并行化for循环


35

Python中是否有任何类似Matlab parfor的工具?我找到了这个线程,但是已经有四年了。我认为也许有人在这里可能有更多的近期经验。

这是我要并行化的事物类型的示例:

X = np.random.normal(size=(10, 3))
F = np.zeros((10, ))
for i in range(10):
    F[i] = my_function(X[i,:])

where my_function取一个ndarraysize (1,3)并返回一个标量。

至少,我想同时使用多个内核,例如parfor。换句话说,假设具有8到16核的共享内存系统。



谢谢@ doug-lipinski。与我在谷歌搜索时发现的其他示例一样,这些示例也基于迭代索引进行了一些琐碎的计算。他们总是声称代码“非常容易”。我的示例在for循环外定义数组(分配内存)。我可以用其他方式来做。这就是我在Matlab中所做的。似乎不适合这些示例的棘手部分是将给定数组的一部分添加到循环内部的函数中。
保罗·君士坦丁

Answers:


19

Joblib做您想要的。基本使用模式是:

from joblib import Parallel, delayed

def myfun(arg):
     do_stuff
     return result

results = Parallel(n_jobs=-1, verbose=verbosity_level, backend="threading")(
             map(delayed(myfun), arg_instances))

其中arg_instancesmyfun并行计算的值列表。主要限制是myfun必须是顶级函数。所述backend参数可以是"threading""multiprocessing"

您可以将其他通用参数传递给并行函数。的主体myfun还可以引用已初始化的全局变量,这些值将对子代可用。

Args和结果对于线程后端几乎可以是任何东西,但是结果需要与多处理后端可序列化。


Dask还提供了类似的功能。如果您正在处理核心数据之外的数据,或者试图并行化更复杂的计算,则可能会更可取。


我看到添加零值以使用包括多处理功能的电池。我敢打赌,joblib在后台使用它。
Xavier Combelle '17

1
必须提到的是joblib不是魔术,threading后端遭受了GIL瓶颈,并且multiprocessing由于所有参数和返回值的序列化,后端带来了巨大的开销。有关Python中并行处理的低级详细信息,请参见此答案
雅库布·克林科夫斯基

我找不到功能复杂性和迭代次数的组合,对于这些组合,joblib会比for循环更快。对我来说,如果n_jobs = 1,它的速度是相同的,并且在所有其他情况下要慢得多
Aleksejs Fomins

@AleksejsFomins基于线程的并行性对于那些不会释放GIL而是释放大量GIL的代码无济于事,尤其是数据科学或数值库。否则,您需要进行多重处理,Jobli会同时支持。现在,多处理模块还具有map可直接使用的并行功能。另外,如果您使用mkl编译的numpy,它将自动进行矢量化操作的并行化,而无需您进行任何操作。默认情况下,Ananconda中的numpy已启用mkl。虽然没有通用的解决方案。Joblib非常低调,2015
Daniel Mahler

谢谢你的建议。我记得以前尝试过多处理,甚至写过几篇文章,因为它没有按我预期的那样扩展。也许我应该再
看一遍

9

您正在寻找的是Numba,它可以自动并行化for循环。从他们的文档

from numba import jit, prange

@jit
def parallel_sum(A):
    sum = 0.0
    for i in prange(A.shape[0]):
        sum += A[i]

    return sum

8

如果不假设在my_function选择multiprocessing.Pool().map()上有什么特别的事情,那么可以并行处理这样的简单循环。joblibdaskmpi计算或numba像在其他的答案提出的长相并不带来任何优势,这样的使用情况,并添加无用的依赖关系(来总结他们是矫枉过正)。按照另一个答案中的建议使用线程不太可能是一个好的解决方案,因为您必须熟悉代码的GIL交互,或者代码应该主要进行输入/输出。

那样说numba可能是加快顺序的纯python代码的一个好主意,但是我觉得这超出了问题的范围。

import multiprocessing
import numpy as np

if __name__ == "__main__":
   #the previous line is necessary under windows to not execute 
   # main module on each child under windows

   X = np.random.normal(size=(10, 3))
   F = np.zeros((10, ))

   pool = multiprocessing.Pool(processes=16)
   # if number of processes is not specified, it uses the number of core
   F[:] = pool.map(my_function, (X[i,:] for i in range(10)) )

但是,有一些警告(但不会影响大多数应用程序):

  • 在Windows下,不支持fork,因此在每个子代启动时都会启动带有主模块的解释器,因此可能会产生开销(这是导致错误的原因。 if __name__ == "__main__"
  • my_function的参数和结果已腌制和未腌制,这可能是太大的开销,请参见此答案以减少它https://stackoverflow.com/a/37072511/128629。这也使不可拾取的对象不可用
  • my_function不应像共享全局变量那样依赖共享状态,因为状态不会在进程之间共享。纯函数(数学意义上的函数)是不共享状态的函数的示例

6

我对parfor的印象是MATLAB正在封装实现细节,因此它可以同时使用共享内存并行性(这是您想要的)和分布式内存并行性(如果您正在运行MATLAB分布式计算服务器)。

如果您想要共享内存并行性,并且正在执行某种任务并行循环,那么多处理标准库包可能就是您想要的,也许有一个不错的前端,例如joblib,如Doug的文章中所述。标准库不会消失,而且会得到维护,因此风险很低。

还有其他选择,例如Parallel PythonIPython的parallel功能。快速浏览Parallel Python使我认为它更接近parfor的精神,因为该库封装了分布式案例的详细信息,但是这样做的代价是必须采用它们的生态系统。使用IPython的成本是相似的。您必须采用IPython的处理方式,这对您来说可能不值得。

如果您关心分布式内存,建议使用mpi4py。Lisandro Dalcin做得很好,在PETSc Python包装器中使用了mpi4py,所以我认为它不会很快消失。就像多处理一样,它是并行的低级接口,而不是parfor,但可能会持续一段时间。


谢谢@Geoff。您有使用这些库的经验吗?也许我会尝试在共享内存机器/多核处理器上使用mpi4py。
Paul G. Constantine

@PaulGConstantine我已经成功使用了mpi4py;如果您熟悉MPI,这将非常轻松。我没有使用多处理,但我已将其推荐给同事,后者说这对他们很有效。我也使用过IPython,但没有使用并行性功能,因此我无法说出它的工作原理。
Geoff Oxberry,2015年

1
Aron在Supercomputing的PyHPC课程中准备了一个很好的mpi4py教程:github.com/pyHPC/pyhpc-tutorial
Matt Knepley 2015年

4

在寻找可用于并行执行“通用” python函数的“黑匣子”工具之前,我建议先分析一下如何my_function()手动进行并行化。

首先,将的执行时间my_function(v)与python for循环开销进行比较:[C] Python for循环非常慢,因此花费的时间my_function()可以忽略不计。

>>> timeit.timeit('pass', number=1000000)
0.01692986488342285
>>> timeit.timeit('for i in range(10): pass', number=1000000)
0.47521495819091797
>>> timeit.timeit('for i in xrange(10): pass', number=1000000)
0.42337894439697266

第二次检查是否存在my_function(v)不需要循环的简单向量实现:F[:] = my_vector_function(X)

(这两点很不重要,如果我在这里提到它们只是为了完整性,请原谅我。)

至少对于CPython实现而言,第三点也是最重要的一点是检查my_function大部分时间是否花费在全局解释器锁(即GIL)之外。如果在GIL之外花费时间,则应使用标准库模块。(这里是一个例子)。顺便说一句,人们可能会认为只是为了发布GIL而将其编写为C扩展。threadingmy_function()

最后,如果my_function()不释放GIL,则可以使用multiprocessing模块

参考资料:有关并行执行的Python文档,以及有关并行处理的numpy / scipy介绍


2

您可以尝试朱莉娅。它非常接近Python,并且具有许多MATLAB构造。这里的翻译是:

F = @parallel (vcat) for i in 1:10
    my_function(randn(3))
end

这也使随机数平行,并且仅在归约期间将结果串联在一起。这使用了多重处理(因此您需要addprocs(N)在使用之前添加进程,并且这也可以在HPC的多个节点上使用,如本博文所示)。

您也可以使用pmap

F = pmap((i)->my_function(randn(3)),1:10)

如果需要线程并行性,则可以使用Threads.@threads(尽管请确保使算法成为线程安全的)。在打开Julia之前,设置环境变量JULIA_NUM_THREADS,然后为:

Ftmp = [Float64[] for i in Threads.nthreads()]
Threads.@threads for i in 1:10
    push!(Ftmp[Threads.threadid()],my_function(randn(3)))
end
F = vcat(Ftmp...)

在这里,我为每个线程创建一个单独的数组,这样它们在添加到数组时就不会发生冲突,然后在之后将它们连接起来即可。线程是相当新的东西,所以现在只是直接使用线程,但是我敢肯定,线程减少和映射将被添加,就像多处理一样。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.