使用numpy数组和共享内存并行化python循环

时间:2012-10-25 12:37:25

标签: python numpy parallel-processing multiprocessing shared-memory

我知道关于这个主题的几个问题和答案,但是没有找到满意的答案来解决这个问题:

通过numpy / scipy函数操作numpy数组的python循环的简单共享内存并行化的最简单方法是什么?

我不是在寻找最有效的方法,我只想要一些简单的实现,当循环不是并行运行时不需要重写。就像OpenMP在低级语言中实现一样。

我在这方面看到的最佳答案是this one,但这是一种相当笨重的方式,需要人们将循环表达为一个带有单个参数的函数,几行共享数组转换crud,似乎要求从__main__调用并行函数,并且从交互式提示符(我花费大量时间)中看起来效果不佳。

使用Python的所有简单性是否真的是最好的循环方式?真?这在OpenMP方式中并行化是微不足道的。

我仔细阅读了多处理模块的不透明文档,但却发现它非常通用,除了简单的循环并行化之外它似乎适合所有东西。我对设置Managers,Proxies,Pipes等不感兴趣。我只是有一个简单的循环,完全并行,在任务之间没有任何通信。使用MPI来平行这样一个简单的情况似乎有些过分,更不用说在这种情况下它将是内存效率低下的。

我没有时间了解Python的众多不同的共享内存并行程序包,但是想知道是否有人在这方面有更多的经验并且可以向我展示一种更简单的方法。请不要建议串行优化技术,如Cython(我已经使用过它),或者使用BLAS等并行numpy / scipy函数(我的情况更通用,更平行)。

3 个答案:

答案 0 :(得分:16)

使用Cython并行支持:

# asd.pyx
from cython.parallel cimport prange

import numpy as np

def foo():
    cdef int i, j, n

    x = np.zeros((200, 2000), float)

    n = x.shape[0]
    for i in prange(n, nogil=True):
        with gil:
            for j in range(100):
                x[i,:] = np.cos(x[i,:])

    return x

在2核机器上:

$ cython asd.pyx
$ gcc -fPIC -fopenmp -shared -o asd.so asd.c -I/usr/include/python2.7
$ export OMP_NUM_THREADS=1
$ time python -c 'import asd; asd.foo()'
real    0m1.548s
user    0m1.442s
sys 0m0.061s

$ export OMP_NUM_THREADS=2
$ time python -c 'import asd; asd.foo()'
real    0m0.602s
user    0m0.826s
sys 0m0.075s

并行运行良好,因为np.cos(和其他ufuncs一样)会释放GIL。

如果您想以交互方式使用此功能:

# asd.pyxbdl
def make_ext(modname, pyxfilename):
    from distutils.extension import Extension
    return Extension(name=modname,
                     sources=[pyxfilename],
                     extra_link_args=['-fopenmp'],
                     extra_compile_args=['-fopenmp'])

和(首先删除asd.soasd.c):

>>> import pyximport
>>> pyximport.install(reload_support=True)
>>> import asd
>>> q1 = asd.foo()
# Go to an editor and change asd.pyx
>>> reload(asd)
>>> q2 = asd.foo()

所以是的,在某些情况下,您可以通过使用线程进行并行化。 OpenMP只是一个花哨的线程包装器,因此只需要Cython就可以获得更简单的语法。没有Cython,你可以使用threading模块---类似于多处理(可能更强大),但你不需要做任何特殊的事情来将数组声明为共享内存。

但是,并非所有操作都会释放GIL,因此YMMV会发布性能。

***

另一个可能有用的链接从其他Stackoverflow答案中删除 - 另一个多处理接口:http://packages.python.org/joblib/parallel.html

答案 1 :(得分:2)

使用映射操作(在这种情况下为multiprocessing.Pool.map())或多或少是在单个机器上对循环进行并行化的规范方法。除非并且直到内置的map()被剔除。

可以找到不同可能性的概述here

你可以使用openmp with python(或者更确切地说是cython),但看起来并不容易。

IIRC,因为与Windows的兼容性而只是从__main__运行多处理内容是必要的。由于windows缺少fork(),它会启动一个新的python解释器,并且必须在其中导入代码。

修改

Numpy可以对dot()vdot()innerproduct()等一些操作进行并列化,当配置好的多线程BLAS库时,例如multiprocessing.Pool,{{1}}和{{1}}。 OpenBLAS。 (另见this question。)

由于numpy数组操作主要由元素构成,因此似乎可能来并行化它们。但是这将涉及为python对象设置共享内存段,或者将数组分成几部分并将它们提供给不同的进程,这与{{1}}的做法不同。无论采用何种方法,都会产生内存和处理开销来管理所有这些。人们将不得不进行大量的测试,以确定哪些数量的阵列实际上值得付出努力。这些测试的结果可能因硬件架构,操作系统和RAM数量而有很大差异。

答案 2 :(得分:0)

.map( )中mathDict()类的ParallelRegression方法完全符合您在两行代码中寻找的内容,这些代码在交互式提示下应该非常容易。它使用真正的多处理,因此要求并行运行的函数是可以发现的,这是不可避免的,但这确实提供了一种从多个进程循环共享内存中的矩阵的简单方法。

假设你有一个可以腌制的功能:

def sum_row( matrix, row ):
    return( sum( matrix[row,:] ) )

然后你只需要创建一个表示它的mathDict()对象,并使用mathDict()。map():

matrix = np.array( [i for i in range( 24 )] ).reshape( (6, 4) )

RA, MD = mathDictMaker.fromMatrix( matrix, integer=True )
res = MD.map( [(i,) for i in range( 6 )], sum_row, ordered=True )

print( res )
# [6, 22, 38, 54, 70, 86]

文档(上面的链接)解释了如何将位置和关键字参数的组合传递到函数中,包括矩阵本身在任何位置或作为关键字参数。这使您几乎可以使用已编写的任何函数而无需修改它。