OpenMP和Python

84

我有编写OpenMP代码的经验,用于共享内存机器(使用C和FORTRAN两种语言),执行简单的任务,例如矩阵加法、乘法等(只是为了查看它与LAPACK相比的效果)。我对OpenMP有足够的了解,可以在不需要查看文档的情况下执行简单的任务。

最近,我转向在我的项目中使用Python,但除了绝对基础知识之外,我没有任何使用Python的经验。

我的问题是:

在Python中使用OpenMP的最简单方法是什么?最简单的方法是指程序员方面付出最少的努力(即使这会增加系统时间成本)?

我使用OpenMP的原因是将串行代码转换为工作并行代码所需的工作量很小,只需在代码中添加几个! $ OMP 即可实现。实现粗略并行化所需的时间非常短。是否有任何方法可以在Python中复制此功能?

从浏览SO上的内容,我可以找到:

  • C扩展
  • StackLess Python

还有其他的吗? 哪一个最符合我的问题?

7个回答

55

Cython

Cython支持OpenMP:使用Cython,可以通过使用prange(并行范围)运算符并将-fopenmp编译器指令添加到setup.py中来添加OpenMP。

在prange土台中工作时,由于使用with nogil:来指定禁用GIL的块,因此执行是并行执行的。

要编译cython_np.pyx,我们必须修改setup.py脚本,如下所示。 我们命令它在编译过程中通知C编译器使用-fopenmp作为参数-启用OpenMP并链接OpenMP库。

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
setup(
    cmdclass = {"build_ext": build_ext},
    ext_modules = [
        Extension(
            "calculate",
            ["cython_np.pyx"],
            extra_compile_args = ["-fopenmp"],
            extra_link_args = ["-fopenmp"]
        )
    ]
)

通过Cython的prange,我们可以选择不同的调度方式。使用静态(static),工作负载会均匀地分布在可用的CPU上。然而,由于某些计算区域的时间成本很高,而其他区域的成本较低 - 如果我们要求Cython在CPU上使用静态(static)平均分配工作块,那么一些区域的结果将比其他区域更快完成,而这些线程将闲置。 动态(dynamic)导向(guided)调度选项都尝试通过在运行时动态分配较小的工作块来缓解此问题,以使CPU在工作负载的计算时间变化较大时更均匀地分布。因此,在您的代码中,正确的选择取决于工作负载的性质。

Numba

Numba的高级版本NumbaPro支持使用OpenMP操作符prange进行并行化的实验性支持。

Pythran

Pythran(用于Python的C ++编译器的子集)可以利用矢量化和基于OpenMP的并行化可能性,但仅在Python 2.7中运行。您可以使用pragma omp指令指定并行部分(与上面描述的Cython的OpenMP支持非常相似),例如:

pragma omp

PyPy

JIT Python编译器PyPy支持多进程模块(请参见以下内容)并且具有一个名为PyPy-STM的项目,这是“在同一进程中并行运行多个独立的CPU密集线程的特殊开发版本的PyPy”。

附注:multiprocessing

OpenMP是多核接口。您可能需要查看multiprocessing。multiprocessing模块在更高级别上工作,共享Python数据结构,而OpenMP在编译为C后使用C原始对象(例如,整数和浮点数)。如果您正在编译代码,则使用OpenMP才有意义;如果您没有编译代码(例如,如果您正在使用高效的numpy代码并希望在多个核心上运行),则坚持使用multiprocessing可能是正确的方法。


1
@boardrider,你能更新一下这个答案吗?因为它非常详细和有解释性。谢谢。 - Ronald Luc
prange 现在似乎已经在 Numba 中普遍可用了。您可以在 https://numba.readthedocs.io/en/stable/user/parallel.html 上找到更多相关信息。 - shadowtalker

37
由于GIL的存在,使用线程在CPython中进行CPU密集型任务是没有意义的。你需要使用多进程(示例)或者使用释放GIL的C扩展库来进行计算,例如某些numpy函数,示例
您可以轻松编写使用多线程的C扩展库,例如Cython,示例

1
Python(正则表达式)中的ctypes有什么评论吗? - user1132648
@Inquest:ctypes允许你在纯Python中调用C库中的一些函数。 - jfs
是的,我知道这一点。我正在征求您对在Python中使用ctypes进行并行编程的可行性的评论。 - user1132648
4
如果几乎所有的工作都在C函数中完成,而你的Python只是一个调用这些函数的包装器,那么ctypes对并行化来说才真正有帮助。这不是真正的“Python并行编程”,但有时很方便。 - Danica
1
ctypes 可以让您在 C 中访问共享内存。因此,它对于并行编程非常有用,如果您需要在进程之间共享大量数据,则可以显著提高速度。 multiprocessing 和其他 Python 模块具有通过 ctypes 利用共享内存的方法。 - Mike McKerns

17
据我所知,Python没有OpenMP包(即使有,我也不知道它会做什么)。如果您想要直接控制线程,您将需要使用其中一个线程库。然而,正如其他人指出的那样,全局解释器锁(GIL)使得在Python中进行多线程编程以提高性能有点...不切实际。GIL意味着一次只有一个线程可以访问解释器。
我建议看看NumPy/SciPy。NumPy允许您编写类似于Matlab的代码,其中您可以使用单个操作来操作数组和矩阵。它还具有一些并行处理功能,请参见SciPy Wiki
其他开始查找的地方:

* 好的,它并不是毫无意义的,但除非时间在Python代码之外被消耗(例如通过popen或类似方法调用的外部进程),否则线程除了方便之外并不能为您带来任何好处。


那你甚至无法编译OpenMP C代码并从Python调用二进制文件吗?为什么会这样呢? - MySchizoBuddy
2
@MySchizoBuddy - 是的,您可以编写OpenMP C代码,将其编译并从Python中调用。 由于它不是由Python引擎执行的,因此它在GIL范围之外执行。 虽然没有直接使用OpenMP的方法在Python中,但是有一些方式可以实现。 - Nathan
“map reduce”在IPython中使用是否可接受,即使它主要是为大数据设计的? - MySchizoBuddy
MapReduce 是一种函数式编程概念,其中将数据映射到函数,然后减少函数结果(例如将它们的答案相加) 。MapReduce 大数据范式将数据映射到若干个工作进程,对这些数据执行某些操作,然后将数据缩减以提供答案。MapReduce 的优势在于其约束简化了代码并行化的过程。挑战是将问题分解以适应 MapReduce 约束。如果 iPython 拥有分布式 MapReduce,您可以从中获得并行性,但需要付出努力。 - Nathan

12

如果您想释放GIL并使用OpenMP,可以查看Cython。它为一些常见任务提供了简单的并行性。您可以在Cython 文档中了解更多信息。


9

有一个叫做pymp的包,作者将其描述为一种将OpenMP类似功能引入Python的包。我已经尝试使用它来处理文件,它可以正常工作。我认为它非常简单易用。 以下是从GitHub页面中摘取的示例:

import pymp
ex_array = pymp.shared.array((100,), dtype='uint8')
with pymp.Parallel(4) as p:
    for index in p.range(0, 100):
        ex_array[index] = 1
        # The parallel print function takes care of asynchronous output.
        p.print('Yay! {} done!'.format(index))

9
也许您的答案在Cython中:
"Cython通过cython.parallel模块支持本地并行处理。若要使用此类并行处理,需要释放GIL(请参见释放GIL)。目前它支持OpenMP,但以后可能会支持更多的后端。" Cython文档

2
Cython结合OpenMP在多核CPU上非常棒,与单线程相比,在一台8核机器上可以获得700%的加速。 - Rabih Kodeih

0

http://archive.euroscipy.org/talk/6857 "介绍了Cython的OpenMP功能,重点是针对NumPy数组的并行循环。源代码示例演示了如何从Python中使用OpenMP。使用OpenMP进行并行算法的结果显示了与其他并行化策略相比,不同数据大小可以实现什么样的加速比。"

import numpy
import cython
from cython cimport parallel

@cython.boundscheck(False)
@cython.wraparound(False)
def func(object[double, ndim=2] buf1 not None,
        object[double, ndim=2] buf2 not None,
        object[double, ndim=2] output=None,
        int num_threads=2):
    cdef unsigned int x, y, inner, outer
    if buf1.shape != buf2.shape:
        raise TypeError('Arrays have different shapes: %s, %s' % (buf1.shape,
            buf2.shape))
    if output is None:
        output = numpy.empty_like(buf1)
    outer = buf1.shape[0]
    inner = buf1.shape[1]
    with nogil, cython.boundscheck(False), cython.wraparound(False):
        for x in parallel.prange(outer, schedule='static',
                num_threads=num_threads):
            for y in xrange(inner):
                output[x, y] = ((buf1[x, y] + buf2[x, y]) * 2 +
                    buf1[x, y] * buf2[x, y])
return output

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