在上一篇中我们简要地介绍了 mpi4py 中的单边通信概念,下面我们将介绍单边通信的相关操作。
创建/释放窗口对象
注意:在使用单边通信操作之前,所有进程都须通过共同参与的创建窗口操作公开声明自己可供访问的内存空间。
创建和释放窗口对象的方法(MPI.Win 类方法)接口如下:
Create(type cls, memory, int disp_unit=1, Info info=INFO_NULL, Intracomm comm=COMM_SELF)
创建并返回用于单边通信的窗口对象。在组内通信子 comm
所指定的通信子范围内所有进程上执行集合操作,每个进程通过一块内存缓冲区 memory
指定要创建的窗口,可以为 None(或 MPI.BOTTOM),此时不提供可供其它进程访问的窗口。disp_unit
指定在远端内存访问操作中的地址单位,即 origin 所指定的位置在 target 一侧要以 target 进程所指定的 diap_unit
为单位计算。通常如果采用相同类型创建窗口,则统一将 disp_unit
设置成 1 即可。如果有的进程需要以组合数据类型(type)给出缓冲区,则可能需要指定 disp_unit
为 sizeof(type)。info
对象用于为 MPI 环境提供优化所需的辅助信息,目前可用的 key 为 no_lock,如果该值设置为 True,则指示 MPI 环境不对进程的本地窗口加锁,也就是说此时表示应用程序可以确信相应窗口不会作为第三方参与通信,这样就可减少相应进程异步代理机制上的一些额外操作。参与创建窗口的进程可分别指定不同的 memory
,disp_unit
和 info
参数,内存中的同一块区域也可同时存在于多个窗口中,只要应用程序能确保并发访问这块区域所需的安全语义。
Free(self)
释放当前窗口对象。会在所有进程间实施 barrier 同步操作,直至所有进程都执行完毕才返回。所有进程都必须在其远端内存访问结束后才可调用此操作。
通信操作
单边通信的 3 种操作方法(MPI.Win 类方法)接口如下:
Put(self, origin, int target_rank, target=None)
该操作把当前进程 origin
中的数据传输到进程 target_rank
的 target
位置。参数 origin
应该是一个长度为2或3的 list 或 tuple,类似于 [data, MPI.DOUBLE]
,或者 [data, count, MPI.DOUBLE]
,以指明发送数据缓冲区,数据计数以及数据类型。当 count
省略时会利用 data
的字节长度和数据类型计算出对应的 count
。对 numpy 数组,其计数和数据类型可以自动推断出来,因此可以直接以 data
作为第一个参数传给 origin
。target
可以是 None,一个整数或是一个长度为3的 list 或 tuple,类似于 [target_disp, target_count, target_datatype]
,分别指定接收到的数据写入 target_rank
进程相对于窗口内存缓冲区的起始位置的偏移,数据量和数据类型,当为 None 或一个整数时,会设置 target_disp
为 0 或该整数,而 target_count
和 target_datatype
会设置成目标窗口缓冲区的大小和数据类型。与点到点通信类似,target 进程缓冲区的大小也需要有足够的空间以容纳要传输的数据。如果 target_datatype
是自定义数据类型,则必须仅包含相对偏移,而不能使用绝对地址,这一限制对后面的 Get 和 Accumulate 方法同样适用。
此操作的实际效果相当于执行一次点到点通信,在源进程执行 Send(origin, dest=target_rank, tag=tag),在目标进程执行 Recv(buf, source=source, tag=tag),不同的是,Put 操作的源进程指定所有通信参数,而不需要在目标进程里启动与之匹配的接收操作。
Get(self, origin, int target_rank, target=None)
该操作从 target_rank
的窗口缓冲区读取数据,各参数的含义及限制与 Put 基本相同,只是数据传输的方向改为由目标流向源。
Accumulate(self, origin, int target_rank, target=None, Op op=SUM)
该操作将 origin
中的数据用 Reduce 中所定义的操作 op
更新 target_rank
的窗口缓冲区中由 target
所指定位置处的数据。各参数的含义及限制与 Put 基本相同, op
指定操作的规约算符。该操作只能使用 Reduce 内置定义的操作和 Accumulate 增加的一个操作——MPI.REPLACE。Put 是 Accumulate 执行 MPI.REPLACE 的特殊情形。可以使用预定义数据类型和用户自定义数据类型,但 target_datatype
不能指定重叠的区域,且目标进程中缓冲区不能超过目标进程所声明窗口的范围。
以上介绍的 Put,Get,Accumulate 均为非阻塞操作,仅当操作发起者对相同的窗口对象调用同步函数(在下一篇中将会介绍)后才能确保实际数据传输完毕。从启动远端内存访问操作起,到通过同步函数确认操作完成之间,不能更新本地进程为实现该次通信所使用的数据缓冲区,即使是 Get 操作,在此期间也不能更新其所使用的本地通信缓冲区。
例程
下面给出单边通信操作相关方法的使用例程。
# win.py
"""
Demonstrates the usage of Create, Free, Put, Get, Accumulate.
Run this with 4 processes like:
$ mpiexec -n 4 python win.py
"""
import numpy as np
from mpi4py import MPI
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
# Create and Put
if rank == 0:
mem = np.array([1, 2], dtype='i')
# create a window object with no accessable memory, used to communicate with other only
win = MPI.Win.Create(None, comm=comm)
# synchronize
win.Fence()
# put data into a memory window of rank 1
print 'rank 0 puts %s to rank 1' % mem
win.Put(mem, target_rank=1)
# synchronize
win.Fence()
else:
# initialize a memory of [0, 0]
mem = np.array([0, 0], dtype='i')
# use mem to create a window for receiving data
win = MPI.Win.Create(mem, comm=comm)
# synchronize
win.Fence()
# synchronize
win.Fence()
print 'rank %d has %s after put' % (rank, mem)
# Get and Accumulate
if rank == 0:
a = np.array(0.5, dtype='d')
# initialize acc with 0.0
acc = np.array(0.0, dtype='d')
# create window objects
win_a = MPI.Win.Create(a, comm=comm)
win_acc = MPI.Win.Create(acc, comm=comm)
# synchronize for win_a
win_a.Fence()
# synchronize for win_a
win_a.Fence()
# synchronize for win_acc
win_acc.Fence()
# synchronize for win_acc
win_acc.Fence()
# after accumulate, print the value of acc = 0.5 + 0.5 + 0.5
print 'rank 0 has acc = %s' % acc
else:
# initialize a with 0.0
a = np.array(0.0, dtype='d')
win_a = MPI.Win.Create(None, comm=comm)
win_acc = MPI.Win.Create(None, comm=comm)
# synchronize for win_a
win_a.Fence()
# get data from a memory window of rank 0
win_a.Get(a, target_rank=0)
# synchronize for win_a
win_a.Fence()
print 'rank %d has a = %s' % (rank, a)
# synchronize for win_acc
win_acc.Fence()
# each rank except 0 accumulates a to the memory window of rank 0
win_acc.Accumulate(a, target_rank=0, op=MPI.SUM)
# synchronize for win_acc
win_acc.Fence()
# free the window object
win.Free()
win_a.Free()
win_acc.Free()
运行结果如下:
$ mpiexec -n 4 python win.py
rank 0 puts [1 2] to rank 1
rank 3 has [0 0] after put
rank 2 has [0 0] after put
rank 1 has [1 2] after put
rank 1 has a = 0.5
rank 3 has a = 0.5
rank 2 has a = 0.5
rank 0 has acc = 1.5
以上我们介绍了 mpi4py 中的单边通信相关操作,在下一篇中我们将介绍单边通信的同步操作。