mpi4py 与 OpenMP 混合编程

上一篇中我们介绍了在 cython 中使用 mpi4py 的方法,下面我们将介绍 mpi4py 与 OpenMP 混合编程。

OpenMP 简介

OpenMP (Open Multi-Processing) 是一个跨平台的多线程实现,它本身不是一种独立的并行语言,而是为多处理器上编写并行程序而设计的、指导共享内存多线程并行的编译制导指令和应用程序编程接口(API),可在 C/C++ 和 Fortran 中使用,一般是在这些编程语言的串行代码中以编译器可识别的注释形式出现。OpenMP 可以在大多数的处理器体系和操作系统中运行,包括 GNU/Linux, Mac OS X 和 Microsoft Windows 等。OpenMP 包括一套编译器指令、库和一些能够影响运行行为的环境变量。在使用 OpenMP 时,主线程(顺序的执行指令)生成一系列的子线程,并将任务划分给这些子线程进行执行。这些子线程并行的运行,由运行时环境将线程分配给不同的处理器。

OpenMP 采用可移植的、可扩展的模型,为程序员提供了一个简单而灵活的开发平台,及从标准桌面电脑到超级计算机的并行应用程序接口。

混合并行编程模型构建的应用程序可以同时使用 OpenMP 和 MPI。

OpenMP 的执行模式

OpenMP 的执行模型采用 fork-join 的形式,其中 fork 创建新线程或者唤醒已有线程;join 即多线程的会合。fork-join 执行模型在刚开始执行的时候,只有一个称为“主线程”的运行线程存在。主线程在运行过程中,当遇到需要进行并行计算的时候,派生出线程来执行并行任务。在并行执行的时候,主线程和派生线程共同工作。在并行代码执行结束后,派生线程退出或者阻塞,不再工作,控制流程回到单独的主线程中。

OpenMP 编程要素

OpenMP 编程模型以线程为基础,通过编译制导指令来显式地指导并行化,OpenMP 提供了三种编程要素来实现对并行化的完善控制,它们是:编译制导、API 函数集和环境变量。

在 C/C++ 和 Fortran 中使用 OpenMP 时需要对 OpenMP 的相关知识足够了解和熟悉,读者可以参考 OpenMP 的相关文档和资料,这里不做进一步的介绍。

在 cython 中使用 OpenMP

不能直接在 Python 中使用 OpenMP,不过幸运的是 cython 支持 OpenMP,而我们可以方便而且容易地用 cython 编写 Python 的扩展模块,或者将 C/C++ 的代码包装成 Python 的扩展模块,因此也就可以间接地在使用 OpenMP 的多线程加速了。

cython 通过其 parallel 模块支持本地并行化,目前该模块支持和使用 OpenMP 的多线程并行方案,今后还可能会支持其它并行机制。要使用 cython 的 parallel 模块以实现并行加速,需要释放 Python 的 GIL (Global Interpreter Lock)。

相关函数

下面是 cython.paralle 中的主要函数:

cython.parallel.prange([start,] stop[, step][, nogil=False][, schedule=None[, chunksize=None]][, num_threads=None])

用于并行的 for 循环,只能用在 Python GIL 被释放的情况下。OpenMP 会自动启动一个线程池并将计算任务按照指定的 schedule 分配给线程池中的线程去完成。在 prange 块下面赋值的变量是最后私有的(lastprivate),即该变量会获得其最后一次迭代的值。如果使用 inplace 算符(如 +=, *= 等)为 prange 块中的变量赋值,则该操作会变成规约运算,即在循环完成后,每个线程本地的该变量副本的值会使用该算符执行规约运算并将运算的结果赋值给此变量。prange 的循环变量总是最后私有的。其参数的意义如下:

  • start:循环变量的起始值。
  • end:循环变量的结束值。
  • step:循环步长,不能为 0.
  • nogil:如果为 True,整个 prange 循环会被包裹在一个 nogil 代码段中,否则需要手动将该循环放入 nogil 块下。
  • schedule:任务分配给线程的调度方式,会传递给 OpemMP,可用的方式有:
    • static:如果设置了 chunksize,计算任务会被提前按照给定的 chunksize 分配到所有线程上。如果没有设置 chunksize,则任务会被划分成近似相同大小的块并提前分配给各个线程。这种分配方式适合于任务能够划分成差不多大小的块并且这些块的执行时间差不多相同的情况。
    • dynamic:任务会被以默认 chunksize 为 1 动态分配给任何空闲的线程。这种方式适合于不同的任务块执行时间不定且事先无法预知的情况。
    • guided:类似于 dynamic 方式,任务会被动态分配给空闲线程,但是分配的任务块会逐步减小(块的大小正比于还未分配的任务数除线程数)。
    • runtime:分配方式和任务块大小在运行过程中确定,如通过 openmp.omp_set_schedule() 函数设置,或通过 OMP_SCHEDULE 环境变量获得。这种方式无法获得在静态编译时的优化,因此在执行性能上可能会差一些。
  • num_threads:使用多少线程。如果没有设置,OpenMP 会决定使用多少线程,一般会使用机器上所有可用的核数。使用的线程数也可以通过调用 omp_set_num_threads() 函数来设置,或者通过 MP_NUM_THREADS 环境变量来设置。
  • chunksize:任务在各个线程上分配的块大小,只对 static,dynamic 和 guided 方式有用。

下面这个例子展示使用 inplace 算符以实现规约运算:

from cython.parallel import prange

cdef int i
cdef int n = 30
cdef int sum = 0

for i in prange(n, nogil=True):
    sum += i

print(sum)
cython.parallel.parallel(num_threads=None)

用在 with 语句中以并行执行一个代码段。可以用来建立会被 prange 使用的线程本地缓冲区。包含在此代码段中的 prange 循环任务会被分配给各个进程协同并行执行。

cython.parallel.threadid()

返回线程 id。对 n 个线程,线程 id 从 0 到 n-1 编号。

使用 OpemMP 函数

从 cython.parallel 中 cimport openmp 后可以使用 OpenMP 中的相关函数,举例如下:

# tag: openmp
# You can ignore the previous line.
# It's for internal testing of the Cython documentation.

from cython.parallel cimport parallel
cimport openmp

cdef int num_threads

openmp.omp_set_dynamic(1)
with nogil, parallel():
    num_threads = openmp.omp_get_num_threads()
    # ...

编译方法

要在 cython 中使用 OpenMP,必须告诉 C/C++ 编译器启用 OpenMP。例如对 gcc 及某些编译器,可以使用类似下面的 setup.py 脚本来编译。

from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize

ext_modules = [
    Extension(
        "hello",
        ["hello.pyx"],
        extra_compile_args=['-fopenmp'],
        extra_link_args=['-fopenmp'],
    )
]

setup(
    name='hello-parallel-world',
    ext_modules=cythonize(ext_modules),
)

mpi4py 与 OpenMP 混合编程

混合编程是指在程序中同时使用多种编程模型,如同时使用 MPI 模型与多线程模型。在MPI 中多线程的使用中我们已经介绍过一种使用 mpi4py 与 Python threading 模块进行混合编程的方法,下面介绍使用 mpi4py 与 OpenMP (借助于 cython)进行混合编程的方法。

上一篇中我们介绍了在 cython 中使用 mpi4py 的方法,现在我们又介绍了在 cython 中使用 OpenMP 的方法,将两种结合起来就能实现 mpi4py 与 OpenMP (借助于 cython)的混合编程。不过需要注意的是,对 Python 对象的很多操作都需要 GIL,而 cython.parallel 的一些操作(如 prange)却需要释放 GIL,因此需要在一些地方使用 with gil 或 wigh nogil 语句以获得或释放 GIL。

例程

下面给出简单的使用例程。

首先是使用了 mpi4py 与 OpenMP 的 cython 代码。

# hello.pyx

from cython import parallel
from mpi4py import MPI


# function that uses MPI and OpenMP hybrid programming
def say_hello(comm):
    cdef unsigned int thread_id
    cdef int i

    # use 2 OpenMP threads to execute the following code
    with nogil, parallel.parallel(num_threads=2):
        # allocate 3 tasks to the 2 threads with a chunk size of 2
        # and a static schedule, so thread 0 will have task 0 and 1,
        # thread 1 will have task 2
        for i in parallel.prange(3, schedule="static", chunksize=2):
            # get the thread id
            thread_id = parallel.threadid()
            # acquire the GIL for Python operation like print
            with gil:
                # get the rank of the MPI process
                rank = comm.rank
                # get the processor name
                pname = MPI.Get_processor_name()
                print 'MPI rank %d, OpenMP thread %d in %s says hello' % (rank, thread_id, pname)

使用下面的脚本将其编译成 Python 扩展模块。

# setup.py

from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize

ext_modules = [
    Extension(
        "hello",
        ["hello.pyx"],
        extra_compile_args=['-fopenmp'],
        extra_link_args=['-fopenmp'],
    )
]

setup(
    name='hello-parallel-world',
    ext_modules=cythonize(ext_modules),
)

编译以上扩展模块的命令如下:

$ python setup.py build_ext --inplace

下面是 Python 测试程序。

# mpi_openmp.py

"""
Demonstrates how to use mpi4py and OpenMP hybrid programming.

2un this with 2 processes like:
$ mpiexec -n 2 python mpi_openmp.py
"""


from mpi4py import MPI
import hello


comm = MPI.COMM_WORLD

hello.say_hello(comm)

执行的结果如下:

$ mpiexec -n 2 -host node1,node2 python mpi_openmp.py
MPI rank 0, OpenMP thread 0 in node1 says hello
MPI rank 0, OpenMP thread 1 in node1 says hello
MPI rank 0, OpenMP thread 0 in node1 says hello
MPI rank 1, OpenMP thread 0 in node2 says hello
MPI rank 1, OpenMP thread 0 in node2 says hello
MPI rank 1, OpenMP thread 1 in node2 says hello

以上介绍了 mpi4py 与 OpenMP 混合编程,在下一篇中我们将介绍在 IPython 中使用 mpi4py。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,479评论 2 59
  • 姓名:唐来宾 学号:17101223417 转载http://mp.weixin.qq.com/s/S6BN8P4...
    ahbz_t阅读 1,207评论 0 1
  • 这场雨 暴雨在先 打得万物生疼 来不及躲避 就湿了衣襟 却又匆匆离去 那份热情 只冲走滚滚烟尘 在一片蛙呜鸟叫声中...
    大清晨的小太阳阅读 258评论 0 8
  • 初识《钢铁是怎样炼成的》是在初中的时候。记得当时老师让我们读这本书,并写下自己的读书心得。年少的我只是草草的摘...
    读书月活动阅读 1,862评论 1 14