MPI消息传递接口(2)——信息传输

来源:互联网 发布:离坚白 知乎 编辑:程序博客网 时间:2024/06/05 03:05

MPI(Message-Passing-Interface)消息传递接口

1.点对点传输

点对点通信.其实就是最简单的进程A向进程B发送信息,而进程B向进程A接收信息.这是关于两个进程之间的通信.

示例代码:

#mpip2p.pyfrom mpi4py import MPIcomm = MPI.COMM_WORLDcomm_rank = comm.Get_rank()comm_size = comm.Get_size()data = [comm_rank]*5comm.send(data,dest=(comm_rank+1)%comm_size)data_recv =comm.recv(source=(comm_rank-1)%comm_size)print "my rank is %d, and Ireceived:" %comm_rankprint data_recv

在命令行中输入命令

mpiexec -n 5 python mpip2p.py

执行结果:

my rank is 4, and Ireceived:[3, 3, 3, 3, 3]my rank is 3, and Ireceived:[2, 2, 2, 2, 2]my rank is 2, and Ireceived:[1, 1, 1, 1, 1]my rank is 0, and Ireceived:[4, 4, 4, 4, 4]my rank is 1, and Ireceived:[0, 0, 0, 0, 0]

指定启动5个mpi进程来执行后面的程序。相当于对脚本拷贝了5份,每个进程运行一份,互不干扰。在运行的时候代码里面唯一的不同,就是各自的rank也就是ID不一样。

Get_rank()函数:获取当前进程rank值

Get_size()函数:获取总共的进程数

send()函数:将数据送给rank为dest的值的进程

recv()函数:接收rank为source的值的数据

消息传递的同步异步性:

recv是阻塞函数,也就是说进程要收到发送方的数据,这个函数才返回.

而send是不确定的,也就是说它有时候是阻塞,有时候是非阻塞.当发送的数据不多的时候,mpi会将数据存到一个系统缓冲区,然后马上进行send方法的返回.而当数据量很大超过缓冲区的大小的时候,mpi需要等待接收方接收,然后把数据拷贝给接收方,再进行send方法的返回.

简单来说,数据量少->非阻塞(同步),数据量大->阻塞(异步).

除了send和recv方法,还有Send和Recv方法.,这样区分是由于要传递的数据的性质差异.当我们要传递int,float,list,dict等python内置类型的数据的时候,我们使用小写的方法.而当使用buffer类型的数据的时候,我们要使用大写的方法.

send的多个版本:

事实上,除了大写小写的版本,send还有不同的版本,这个不同是基于不同的发送策略的,而这些版本都有大小写之分.

bsend:缓冲模式,数据写入缓冲区,马上返回,用户必须确保缓冲区大小足够

ssend:同步模式,等接收方接收才返回

rsend:就绪模式,发送时必须确保接收方处于等待接收的状态,否则产生错误

send:标准模式(bsend+ssend),send实际上就是bsend和ssend的结合体.

2.多点传输:

#mpimp.pyfrom mpi4py import MPIcomm = MPI.COMM_WORLDcomm_rank = comm.rank()comm_size = comm.size()if comm_rank == 0:    data = [1,2,3]    for i in range(comm_size - 1):        comm.send(data,dest=i+1)else:    data = comm.recv(source = 0)    print "Process %d receive"%comm_rank,data 

运行结果:

mpiexec -n 6 python mpimp.py
Process 1 receive [1,2,3]Process 2 receive [1,2,3]Process 3 receive [1,2,3]Process 4 receive [1,2,3]Process 5 receive [1,2,3]

此做法漏洞:

在单机上跑这n个进程好像没所谓,CPU始终在工作,时间复杂度也是O(n)级别.

但假如是n台机器分别跑这n个进程,第0台机器始终在发送数据,而其他机器的大部分时间都在排队,等第0台机器往自己发送数据.这样的话,这堆机器要运行完这堆进程,需要O(n)时间.等于一台机器的工作效率,不是满意的结果。

广播(改进):

想到了,我们可以像p2p那样做,有数据的机器都帮忙向没有数据的机器发送数据,这样的话时间复杂度是可以降低到O(logn)的!

mpi有实现这样操作的接口,bcast函数

改进代码:

#mpimp.pyfrom mpi4py import MPIcomm = MPI.COMM_WORLDcomm_rank = comm.rank()comm_size = comm.size()if comm_rank == 0:    data = [1,2,3]    comm.bcast(data, root=0)else:    data = comm.bcast(None, root=0)    print "Process %d receive"%comm_rank,data 

bcast()函数:无论是广播者,还是被广播者,都是调用bcast函数,而不像点对点那样一个send另一个recv.bcast()函数一个根进程把数据发给其他进程。

散播:

这里写图片描述

散播的函数和广播的参数是一样的,只是返回值不一样.

注意!散播的发送方也会接收到数据(和概念图有出入),

散播里列表里元素的分发不是按进程0就分得第0个元素,进程1就第1个元素这样的.而是一种类似随机的打乱的分发策略.

散播发送的数据,data(列表)里元素的个数必须等于进程的个数.否则会出错。

示例代码:

#mpisca.pyfrom mpi4py import MPIcomm = MPI.COMM_WORLDcomm_rank = comm.Get_rank()comm_size = comm.Get_size()if comm_rank == 0:    data = [1,2,3,4,5,6]else:    data = Nonedata = comm.scatter(data, root=0)    print "Process %d receive"%comm_rank,data 

运行结果:

mpiexec -n 6 python mpisca.pyProcess 1 receive 2Process 4 receive 5Process 2 receive 3Process 0 receive 1Process 3 receive 4Process 5 receive 6

收集:

散播的逆操作:

#mpigather.pyfrom mpi4py import MPIcomm = MPI.COMM_WORLDcomm_rank = comm.Get_rank()comm_size = comm.Get_size()if comm_rank == 0:    data = comm.gather(comm_rank, root=0)    print dataelse:    comm.gather(comm_rank,root=0)
mpiexec -n 8 python mpigather.py[0, 1, 2, 3, 4, 5, 6, 7]

reduce()规约函数:

它相当于在收集的过程中不断地进行两元运算,最终在接收方那里只有一个值,而不是一个列表.

也就是说规约函数

示例代码:通过113+1517+...=π4计算圆周率

#mpireduce.pyfrom mpi4py import MPIcomm = MPI.COMM_WORLDcomm_rank = comm.Get_rank()comm_size = comm.Get_size()k = (1.0 if comm_rank%2 == 0 else -1.0)/(2*comm_rank +1)data = comm.reduce(k, root=0,op=MPI.SUM)if comm_rank == 0:    pi = data*4    print "PI = %.6f"%pi

运行结果:

C:\Python27\Scripts\ML\MPI>mpiexec -n 12 python mpireduce.pyPI = 3.058403

注意事项:

1.并行计算的reduce,scatter,gather在执行信息交互函数是并行,信息交互完之后,每个进程统一从函数中出来,执行接下来的代码

2.上述函数root秩代表根节点:scatter传播,gather接收,reduce最终汇总结果的进程,

3每台机器reduce复杂度,只有O(logn),reduce函数MPI_SUMj操作:

假设九个进程

1, 2, 3, 4, 5, 6, 7, 8, 9  1+2, 3+4, 5+6, 7+8, 9   1+2+3+4, 5+6+7+8, 9    1+2+3+4+5+6+7+8, 9    1+2+3+4+5+6+7+8+9

4.单机的话不要开几百个进程,不是开玩笑的

5.注意的是,散播和reduce中发送接收到的返回值,不是接收方最终得到的返回值,而是一个none.

alltogether:收集后再广播一次,allreduce:reduce+bcast

barrier是一种全局同步,就是说全部进程进行同步.

当一个进程调用barrier的时候,它会被阻塞.

当所有进程都调用了barrier之后,barrier会同时解除所有进程的阻塞.

但运行起来发现并不是这回事.所有进程没有像期待那样先全部输出begin,再全部输出end,barrier这个函数仿佛形同虚设.

其实这里问题不是在barrier,而是在print.

我们OS的IO是有缓冲的,一个数据要出现在屏幕上,简单来说是经过内存->标准IO文件->控制台屏幕.

而进程间不共享IO文件(后面会学到如何在MPI的进程里共享文件),共享控制台屏幕.

因此屏幕上语句的顺序依赖OS什么时候将IO文件里的内容推到屏幕上.

我们强制让内存->标准IO文件和标准IO文件->控制台屏幕这两步一起进行,也就是加上flush语句.

form mpi4py import MPIimport syscomm = MPI.COMM_WORLDcomm_rank = comm.Get_rank()comm_size = comm.Get_size()print comm_rank,'begin'sys.stdout.flush()comm.barrier()print comm_rank,'end'

sendrecv()函数

发送send+接收recv

data = sendrecv(data,dest=1)

关于进程

from mpi4py import MPIcomm = MPI.COMM_WORLDcomm_rank = comm.Get_rank()comm_size = comm.Get_size()data_send = [comm_rank]*5comm.send(data_send,dest=(comm_rank+1)%comm_size)data_recv = comm.recv(source=(comm_rank-1)%comm_size)print (my rank is %d, and Ireceived: %comm_rank)print data_recv

这里面有个需要注意的问题,如果我们要发送的数据比较小的话,mpi会缓存我们的数据,然后继续执行后面的指令,而不会等待对方进程执行recv指令接收这个数据。

但是,如果要发送数据量很大,[rank]*500程序就会很卡,因为所有进程都会卡在发送这条指令,等待下一个指令发起接收指令,但是进程是执行完发送的指令才能接收的指令,这就和死锁差不多。

一般修改如下:

from mpi4py import MPIcomm = MPI.COMM_WORLDcomm_rank = comm.Get_rank()comm_size = comm.Get_size()data_send = [comm_rank]*500if comm_rank == 0:    comm.send(data_send, dest=(comm_rank+1)%comm_size)if comm_rank > 0:    data_recv = comm.recv(source=(comm_rank-1)%comm_size)    comm_send(data_send,dest=(comm_rank-1)%comm_size)if comm_rank == 0:    data_recv = comm.recv(source=(comm_rank-1)%comm_size)

这也就是为什么接收放在前面的原因了

原创粉丝点击