MPI-3 中增强的单边通信

上一篇中我们介绍了 MPI-3 中引进的非阻塞通信子复制和组集合通信子创建方法,下面我们将介绍 MPI-3 中增强的单边通信方法。

MPI-2 引进了单边通信,也称远端内存访问(Remote Memory Access,RMA)。单边通信操作可以提供更高的性能(在有好的硬件支撑的情况下)和更强的功能,简化某些并行编程任务。在前面我们介绍过 MPI-2 引进的单边通信的相关概念和操作方法。MPI-3 极大地扩展了 MPI-2 的单边通信功能,澄清了一些比较模糊的概念,并且解决了限制 MPI-2 的单边通信广泛应用的一些限制。

方法接口

下面给出 MPI-3 中扩展的单边通信相关方法(MPI.Win 类方法)。

窗口创建

Create_dynamic(type cls, Info info=INFO_NULL, Intracomm comm=COMM_SELF)

创建并返回用于单边通信的窗口对象,返回的窗口对象没有为其分配的内存。在组内通信子 comm 所指定的通信子范围内所有进程上执行集合操作。info 对象用于为 MPI 环境提供优化所需的辅助信息,可用的 key 有:

  • no_locks:如果为 True,会假定所创建的窗口不会被加锁(即调用 MPI.Win.Lock 或者 MPI.Win.Lock_all)。如果只使用主动同步,则可以设定此 key 为 True,此时 MPI 实现可对单边操作做一些优化。
  • accumulate_ordering:默认情况下对同一个窗口的多个 Accumulate 操作会按照严格的调用顺序执行。如果将此 key 设置成 none,则 MPI 实现不会保证对同一个窗口的多个 Accumulate 操作的执行顺序。该 key 也可以设置成一系列由逗号分隔的 rar (read-after-read), war (write-after-read), raw (read-after-write) 和 waw (write-after-write) 以指明相应的执行顺序。放松对执行顺序的限制可能会提高执行效率。
  • accumulate_ops:如果设置成 same_op,MPI 实现会假定对窗口的同一目的地址的并发 Accumulate 调用都会使用同样的算符。如果设置成 same_op_no_op,MPI 实现会假定对窗口的同一目的地址的并发 Accumulate 调用都会使用同样的算符或者 MPI.NO_OP。该 key 的默认值为 same_op_no_op。
Allocate(type cls, Aint size, int disp_unit=1, Info info=INFO_NULL, Intracomm comm=COMM_SELF)

分配指定大小的内存并创建和返回用于单边通信的窗口对象。在组内通信子 comm 所指定的通信子范围内所有进程上执行集合操作。每个进程所返回的窗口对象会包含一块分配好的 size 大小的内存。每个进程的 size 可以不同,甚至可以为 0。disp_unit 指定在远端内存访问操作中的地址单位,即 origin 所指定的位置在 target 一侧要以 target 进程所指定的 diap_unit 为单位计算。通常如果采用相同类型创建窗口,则统一将 disp_unit 设置成 1 即可。如果有的进程需要以组合数据类型(type)给出缓冲区,则可能需要指定 disp_unit 为 sizeof(type)。info 对象用于为 MPI 环境提供优化所需的辅助信息。

Allocate_shared(type cls, Aint size, int disp_unit=1, Info info=INFO_NULL, Intracomm comm=COMM_SELF)

创建并返回一个拥有共享内存的窗口对象。MPI-3 共享内存相关操作将在下一篇中介绍。

Attach(self, memory)

给当前窗口对象对象(由 Create_dynamic 所创建的窗口对象)加载一块内存 memory

Detach(self, memory)

卸载当前窗口对象(由 Create_dynamic 所创建的窗口对象)的内存 memory

单边操作

Rput(self, origin, int target_rank, target=None)

对应 MPI.Win.Put 操作,参数也同 MPI.Win.Put,不同的是该方法返回一个 MPI.Request 对象,该对象的完成(可通过 Wait,Test 等)意味着发送的进程可以更改其 origin 数据缓冲区,但并不表示目标进程的窗口已经完成了数据的接收(需要通过 MPI.Win.Flush,MPI.Win.Flush_all,MPI.Win.Unlock,MPI.Win.Unlock_all 等来完成)。

Rget(self, origin, int target_rank, target=None)

对应 MPI.Win.Get 操作,参数也同 MPI.Win.Get,不同的是该方法返回一个 MPI.Request 对象,该对象的完成(可通过 Wait,Test 等)意味着 origin 缓冲区已经从远端拿到数据。

Raccumulate(self, origin, int target_rank, target=None, Op op=SUM)

对应 MPI.Win.Accumulate 操作,参数也同 MPI.Win.Accumulate,不同的是该方法返回一个 MPI.Request 对象,该对象的完成(可通过 Wait,Test 等)意味着发送的进程可以更改其 origin 数据缓冲区,但并不表示目标进程的窗口已经完成了数据的更新(需要通过 MPI.Win.Flush,MPI.Win.Flush_all,MPI.Win.Unlock,MPI.Win.Unlock_all 等来完成)。

Get_accumulate(self, origin, result, int target_rank, target=None, Op op=SUM)

该操作将 origin 中的数据用 Reduce 中所定义的操作 op 更新 target_rank 的窗口缓冲区中由 target 所指定位置处的数据,并通过 result 返回target_rank 的窗口缓冲区中未被 accumulate 更新前的数据。除 result 外,其它参数同 MPI.Accumulate 方法。op 可以使用 Reduce 内置定义的操作,MPI.REPLACE 和 MPI.NO_OP,但是不能使用用户自定义的算符。操作的数据只能是预定义的数据类型或由同一种预定义数据类型创建的用户自定义数据类型。

Rget_accumulate(self, origin, result, int target_rank, target=None, Op op=SUM)

对应 MPI.Win.Get_accumulate 操作,参数也同 MPI.Win.Get_accumulate,不同的是该方法返回一个 MPI.Request 对象,该对象的完成(可通过 Wait,Test 等)意味着发送的进程可以更改其 origin 数据缓冲区,并且 result 已经拿到数据,但并不表示目标进程的窗口已经完成了数据的更新(需要通过 MPI.Win.Flush,MPI.Win.Flush_all,MPI.Win.Unlock,MPI.Win.Unlock_all 等来完成)。

Fetch_and_op(self, origin, result, int target_rank, Aint target_disp=0, Op op=SUM)

是 Get_accumulate 的简化版本,只能操作单个数据项(由操作的数据类型定义),除 target_disp 外其它参数与 Get_accumulate 的对应参数同。target_disp 指定要操作的远程窗口的起始偏移位置。

Compare_and_swap(self, origin, compare, result, int target_rank, Aint target_disp=0)

compare 中的数据与 target_rank 窗口缓冲区中偏移 target_disp 处的单个数据项进行比较,如果相同则用 origin 中的数据替换target_rank 窗口缓冲区中偏移 target_disp 处的数据,并由 result 返回未被替换前的数据。操作的数据类型只能是以下几种:C 整型、Fortran 整型、布尔型、Byte 型等。

同步操作

Lock_all(self, int assertion=0)

对该窗口对象中的所有进程启动一个访问时间段,加锁的类型为 MPI.LOCK_SHARED(共享锁)。在此访问时间段内调用此方法的进程可以通过单边通信操作访问该窗口对象的所有进程的窗口缓冲区。必须用配对的 Unlock_all 来解锁。该方法不是一个集合操作,ALL 是指该窗口包含的所有进程。assertion 除了默认的 0 之外可以设置为 MPI.MODE_NOCHECK,表示在尝试创建锁时,可以确信没有其它进程已经取得了相同窗口对象的锁,或者正在尝试获取窗口对象的锁。

Unlock_all(self)

与 Lock_all 配对,标记一个访问时间段的结束。该方法返回后在该访问时间段内的所有单边通信操作的源端和目的端都会完成。

Flush(self, int rank)

该方法完成调用进程对 rank 进程的所有单边访问操作,源端和目的端都会完成。只能用在被动同步的访问时间段内。

Flush_all(self)

该方法完成调用进程的所有单边访问操作,源端和目的端都会完成。只能用在被动同步的访问时间段内。

Flush_local(self, int rank)

该方法完成调用进程对 rank 进程的所有单边访问操作的本地源端缓冲区。该方法返回后当前进程就可以重新使用提供给 Put,Get,Accumulate 方法的 origin 缓冲区。只能用在被动同步的访问时间段内。

Flush_local_all(self)

该方法完成调用进程的所有单边访问操作的本地源端缓冲区。该方法返回后当前进程就可以重新使用提供给 Put,Get,Accumulate 方法的 origin 缓冲区。只能用在被动同步的访问时间段内。

Sync(self)

同步当前窗口的私有和公共内存区。它并不会完成一些尚未完成的单边通信操作。

例程

下面给出上面介绍的部分方法的使用例程。

# mpi3_rma.py

"""
Demonstrates the usage of MPI-3 enhanced RMA.

Run this with 4 processes like:
$ mpiexec -n 4 python mpi3_rma.py
"""

import numpy as np
from mpi4py import MPI


comm = MPI.COMM_WORLD
rank = comm.Get_rank()

# Create
if rank == 0:
    win = MPI.Win.Create(None, comm=comm)

    # Lock_all
    win.Lock_all()
    for rk in [1, 2, 3]:
        a = np.array([rk, rk], dtype='i')
        # Put
        win.Put(a, target_rank=rk)
        print 'rank %d put %s to rank %d' % (rank, a, rk)
    # Unlock_all
    win.Unlock_all()

    # Lock for rank 1
    win.Lock(rank=1)
    b = np.array([10, 10], dtype='i')
    c = np.array([-1, -1], dtype='i')
    # Get_accumulate
    win.Get_accumulate(b, c, target_rank=1, op=MPI.SUM)
    # Unlock for rank 1
    win.Unlock(rank=1)
    print 'rank %d Get_accumulate %s to rank 1, and get result %s' % (rank, b, c)

    comm.Barrier()
else:
    mem = np.array([-1, -1], dtype='i')
    win = MPI.Win.Create(mem, comm=comm)
    comm.Barrier()

    print 'rank %d get %s' % (rank, mem)


# Allocate
if rank == 0:
    win = MPI.Win.Allocate(0, disp_unit=4, comm=comm)
    # Lock_all
    win.Lock_all()
    reqs = []
    for rk in [1, 2, 3]:
        a = np.array([rk, rk], dtype='i')
        # Rput
        req = win.Rput(a, target_rank=rk)
        reqs.append(req)
        print 'rank %d put %s to rank %d' % (rank, a, rk)
    # compute all Rput
    MPI.Request.Waitall(reqs)
    # Unlock_all
    win.Unlock_all()
    comm.Barrier()
else:
    win = MPI.Win.Allocate(8, disp_unit=4, comm=comm)
    comm.Barrier()
    # convert the memory of win to numpy array
    buf = np.array(buffer(win.tomemory()), dtype='B', copy=False)
    mem = np.ndarray(buffer=buf, dtype='i', shape=(2,))

    print 'rank %d get %s' % (rank, mem)


# Create_dynamic
win = MPI.Win.Create_dynamic(comm=comm)
mem = MPI.Alloc_mem(8)
# Attach and Detach
win.Attach(mem)
win.Detach(mem)
MPI.Free_mem(mem)

运行结果如下:

$ mpiexec -n 4 python mpi3_rma.py 
rank 0 put [1 1] to rank 1
rank 0 put [2 2] to rank 2
rank 0 put [3 3] to rank 3
rank 0 Get_accumulate [10 10] to rank 1, and get result [1 1]
rank 1 get [11 11]
rank 2 get [2 2]
rank 3 get [3 3]
rank 0 put [1 1] to rank 1
rank 0 put [2 2] to rank 2
rank 0 put [3 3] to rank 3
rank 3 get [3 3]
rank 1 get [1 1]
rank 2 get [2 2]

以上介绍了 MPI-3 中增强的单边通信方法,在下一篇中我们将介绍 MPI-3 中共享内存操作。

    原文作者:自可乐
    原文地址: https://www.jianshu.com/p/4687fddde947
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞