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

2024-06-17 12:29:01 发布

您现在位置:Python中文网/ 问答频道 /正文

我知道关于这个问题有几个问题和答案,但还没有找到一个令人满意的答案:

对于通过numpy/scipy函数操作numpy数组的python循环,最简单的方法是什么?

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

在这方面,我看到的最好的答案是this one,但这是一种相当笨拙的方法,它需要将循环表示为一个函数,该函数只接受一个参数,几行共享数组转换crud,似乎需要从__main__调用并行函数,从交互提示(我花了很多时间)来看,它似乎不太好用。

使用Python的所有简单性,这真的是并行化循环的最佳方法吗?真正地?在OpenMP方式中,这是一件很容易并行的事情。

我仔细阅读了多处理模块的不透明文档,结果发现它非常通用,除了简单的循环并行化之外,似乎什么都适合。我对设置管理器、代理、管道等不感兴趣。我只是有一个简单的循环,完全并行,任务之间没有任何通信。使用MPI来并行处理这样一个简单的情况似乎有点过头了,更不用说在这种情况下会导致内存效率低下。

我还没来得及了解Python的许多不同的共享内存并行包,但我想知道是否有人在这方面有更多的经验,并且可以向我展示一种更简单的方法。请不要推荐串行优化技术,如Cython(我已经在使用它),或者使用并行numpy/scipy函数,如BLAS(我的例子更通用,也更并行)。


Tags: 方法函数答案numpy参数main时间情况
3条回答

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

假设你有一个泡菜功能:

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]

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

带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

在双核心机器上:

$ 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(与其他ufunc一样)会释放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

使用映射操作(在本例中是multiprocessing.Pool.map())或多或少是在单个计算机上并行化循环的规范方法。除非和直到内置的map()被并行化。

对不同可能性的概述可以在here中找到。

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

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

编辑

Numpy可以并行化一些操作,比如dot()vdot()innerproduct(),如果配置了一个好的多线程BLAS库,比如OpenBLAS。(另请参见this question。)

由于numpy数组操作主要是按元素进行的,因此似乎可以将它们并行化。但这将涉及为python对象设置一个共享内存段,或者将数组分成多个部分并将它们提供给不同的进程,这与multiprocessing.Pool所做的工作没有什么不同。不管采取什么方法,管理所有这些都会产生内存和处理开销。我们将不得不运行大量的测试,看看这对于哪些大小的数组是值得的。这些测试的结果可能会因硬件架构、操作系统和RAM数量的不同而有很大差异。

相关问题 更多 >