「并行学习」MPI编程
开个新坑www介绍定义MPI = Message Passing Interface,提供Message Passing方式的API来写程序。也就是说,不需要实际去做一个库(Library)函数,可以直接调用MPI的API来写并行程序。好处portable,便携;Scalable,可扩展的;Flexible,灵活。小小的吐槽:最近在看台湾人的网课,一直程式程式的,搞...
MPI编程
简介
定义
 MPI = Message Passing Interface,提供Message Passing方式的API来写程序。也就是说,不需要实际去做一个库(Library)函数,可以直接调用MPI的API来写并行程序。
好处
- portable,便携;
 - Scalable,可扩展的;
 - Flexible,灵活。
 
Programming Model
SPMD
 Single Program Multiple Data。单一的过程中,多个数据或单个程序、多数据计算。
  任务是分裂,并同时运行在多个处理器上,以不同的输入更快地获得结果。

要注意的是:
- 
  
MPI编程需要一开始就决定处理器的数量,不能在运行过程中添加新的线程(MPI增加了添加新线程的API,但不建议使用),也就是一开始就要决定运行MPI程序时的平行度。
 - 
  
每个处理器执行的相同的代码,launch到处理器时,会到不同的brach,同时会分配给每个branch不同的ID,然后通过不同的ID读不同的数据的partition(划分),处理不同的数据。
 
Communication Methods
 这里参考了下这篇博客。
几个定义
一、从communicated processes的角度
 分为Synchronous communication(同步通信)和Asynchronous Communication(异步通信)。
1.Synchronous communication
 Simultaneously(同步)发送/接受数据。
2.Asynchronous Communication
 Non-simultaneously(非同步)发送/接受数据。
二、从function calls的角度
 分为Blocking(阻塞)和Non-blocking(非阻塞)。
1.Blocking
 由于一个function未完成,程序会blocking在一个地方,直到function的完成
 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
2.Non-blocking
 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
 在Linux下,一个socket的文件描述符(file descriptor)默认就是阻塞模式的。在这种模式下,即便这个socket压根没有收到任何数据,我们的
read调用也会一直阻塞在那里,无法返回,直到有数据到达为止。 如果我们把这个socket的文件描述符用
fcntl设置为非阻塞的。在这种模式下,如果这个socket没有收到任何数据,我们的read调用会立刻返回一个错误。这个时候,我们的程序就知道目前没法从这个socket里读到数据了,索性去干点别的事情,过段时间再调用read。当一个应用进程对一个非阻塞的文件描述符循环调用read时,我们称之为轮询(polling)。
 Blocking calls与synchronous、non-blocking calls 与asynchronous之间不是一一对应的关系,但一般来说,我们会使用blocking calls来实现synchronous,使用non-blocking calls来实现asynchronous。
Message Passing
1.Synchronous/Blocking Message Passing

2.Asynchronous/Non-Blocking Message Passing
 与同步数据传输相比,需要加入一个Message Buffer。
 Message Buffer一般是在sender和/或receiver端之间的一段内存空间。因此两者的通信变为:
 sender端传递出数据给message buffer,而不是直接给receiver数据。当receiver执行recv()命令时,从message buffer获取数据。
 但是需要考虑到的是,如果receiver在sender向message buffer之前执行recv(),此时获取的可能是之前存在message buffer的数据或者是空数据,因此需要在message buffer中添加一个flag/status,receiver执行recv()判断message buffer的状态。

MPI API
Getting Start
- 首先
#include mpi.h; - MPI calls:
 

注意:
- MPI的函数一般返回的是一个
Error Code;因此获取数据的时候不是通过返回值获取,而是通过指针获取的。 - 上面的serial code虽然在
MPI_INIT()外,但仍然会重复执行。MPI_INIT()所包含的部分只是说会通过message passing interface来进行沟通。 
Communicators 和 Groups
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9zQdYKTb-1572696279702)(/Users/panyumingzhi/Documents/asc/MPI编程/MPI_1/截屏2019-10-29下午9.17.40.png)]](https://i-blog.csdnimg.cn/blog_migrate/1497c70c82761b2b707d0f980a9aef00.png)
一个group有一个communicator,每一个communicator有一个ID。
默认有一个MPI_COMM_WORLD communicator。
Point-to-Point Communication Routines
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XwyY9tam-1572696279705)(/Users/panyumingzhi/Documents/asc/MPI编程/MPI_1/截屏2019-11-01下午7.04.31.png)]](https://i-blog.csdnimg.cn/blog_migrate/9867b49a6e311877ea53ad7101ee1d19.png)
- 
  
buffer:buffer是一个地址,指到的是要接受或者发送的那个地址。(需要注意的是:首先要allocate一个buffer地址,如果不先allocate一个buffer地址会报错,并且allocate的地址空间要大于等于后面type类型对应的长度 乘以count的大小得出来的size的值) - 
  
type和count决定buffer空间的大小。type决定存放的是什么类型的数据,count决定数据元素的数量,type对应的长度乘以count就是size的大小。 - 
  
type的值为:MPI_CHAR,MPI_SHORT,MPI_INT,MPI_LONG等。它们都是define的值,例如MPI_INT可能实际上对应的是数字4,但我们最好不要直接定义该参数为数字4,因为不同电脑int的长度不同,使用MPI_INT更加保险。 - 
  
source/dest:int类型的值,实际上是sender/receiver的ID。 - 
  
tag:int类型的值,是一个可选项,目的是分传递的数据的类型。发送与接收的tag相同时,才会receiver才会真正的接收message buffer中的数据。如果要接收任意数据,使用MPI_ANY_TAG。 - 
  
request:non-blocking时使用。在non-blocking中,一个动作不论receiver是否接收到,sender发送到message buffer中之后,就会去做其他的事情。同样,receiver不管是否真的发送完消息,接收到message buffer中的数据(即使是一部分数据)后,都会继续去做计算。但有时,receiver只有在真的接收到sender发送的数据时,才能做下一步的计算。因此,如果需要检查sender是否发送完消息,就需要用到这里的request了。也就是说,request的作用就是检查sender端是否发送完了数据。 - 
  
status:实际上是一个结构体,记录了receive的讯息,如API call是否成功,从message buffer中获取了多少数据。 
blocking的例子
MPI_Comm_rank(MPI_COMM_WORLD, &myRank); /* find process rank */
if (myRank == 0) {
	int x=10;
	MPI_Send(&x, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
}else if (myRank == 1) {
	int x;
	MPI_Recv(&x, 1, MPI_INT, 0, MPI_ANY_TAG, MPI_COMM_WORLD,status); 
}
这里要注意的是,两个x并不是share-memory的,第一个x位于sender这个process对应的地址空间上,第二个x位于receiver这个process对应的地址空间上。
non-blocking的例子
MPI_Comm_rank(MPI_COMM_WORLD, &myrank);/* find process rank */
if (myrank == 0) {
	int x=10;
	MPI_Isend(&x, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, req1); 
  compute();
} else if (myrank == 1) { 
  int x;
	MPI_Irecv(&x,1,MPI_INT,0,MPI_ANY_TAG,MPI_COMM_WORLD,req1); 
}
MPI_Wait(req1, status);
MPI_Wait()是一个blocking call,程序会卡在这里,直到req1完成,也就是receiver真正接收到了数据。
MPI_Test()返回一个flag,这个flag代表request是否完成。
Collective Communication Routines
Collective Calls
所有collective calls都是blocking calls,一个group中的所有process都需要执行这个collective calls,否则会卡住。
MPI_Barrier(comm):所有属于comm这个group的任务,所有在这个group的process都需要执行MPI_Barrier(comm)这条命令,否则会程序会卡住。

MPI_Bcast(&buffer,count,datatype,root,comm):将id为root的这个process的buffer中的值broadcast(广播)出去,这条消息将被其他所有process接收到。注意其他所有process的buffer一定要先allocate好,否则将出现问题。![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F4Ho6NDB-1572696279706)(/Users/panyumingzhi/Documents/asc/MPI编程/MPI_1/截屏2019-11-01下午8.28.09.png)]](https://i-blog.csdnimg.cn/blog_migrate/180c232d8898fb111a87c0c79fa5e6ef.png)
MPI_Scatter(&sendbuf,sendcnt,sendtype,&recvbuf,recvcnt,recvtype,root,comm):
从sender端的值,要分配给receiver端。把buffer中在arrange之内的做均分,分配到每一个receiver端。sendcnt表示了sender端counter的大小,recvcnt表示了receiver端counter的大小。使用这个函数,将会把sender端buffer中的内容平均分配,并给到receiver端。但是,如果sender端只能分3块,而receiver有5个,那么会有两个receiver process不会收到消息,程序仍会继续运行。MPI_Gather(&sendbuf,sendcnt,sendtype,&recvbuf,recvcnt,recvtype,root,comm):

从多个send端发送消息,消息汇总到一个receive端中。(与MPI_Scatter()作用相反)
sendcnt:在send buffer中元素的数量;number of elements in send buffer (integer)
recvcnt:一次接收元素的数量。number of elements for any single receive (integer, significant only at root)
MPI_Reduce(&sendbuf,&recvbuf,count,datatype,op,dest,comm):
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-asHzVwFZ-1572696279711)(/Users/panyumingzhi/Documents/asc/MPI编程/MPI_1/截屏2019-11-01下午8.59.55.png)]](https://i-blog.csdnimg.cn/blog_migrate/cf6c4584f434e2a5282a64ad25edb0f4.png)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f2kVzhfB-1572696279711)(/Users/panyumingzhi/Documents/asc/MPI编程/MPI_1/截屏2019-11-01下午9.01.00.png)]](https://i-blog.csdnimg.cn/blog_migrate/c166d7d5e0a570fee222cdcb7f1b0bd5.png)
通过MPI_Reduce()这个函数,将几个process的结果整合到一个process中,并且将数据本身也进行整合。op这个参数,可以选择如何整合:比如,MPI_SUM其他buffer中的结果相加,结果给dest进程的存储在recvbuf中。
MPI_Allgather(&sendbuf,sendcnt,sendtype,&recvbuf,recvcnt,recvtype,root,comm):

MPI_Allreduce(&sendbuf, &recvbuf, count, datatype, op, comm):
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1WWADi96-1572696279712)(/Users/panyumingzhi/Documents/asc/MPI编程/MPI_1/截屏2019-11-02下午6.35.11.png)]](https://i-blog.csdnimg.cn/blog_migrate/5aa6674f7c8f50e7a72f35b372bfa11d.png)
Group and Communicator Routines
先有Group,通过Group创建Communicator。
MPI_Comm_group(Comm,&Group),:通过已知的Communicator,获取对应的group的token。MPI_Group_incl(Group, size, ranks[], &NewGroup):如果要创建新的group,只能从原来的group中选定哪几个作为新group的成员。MPI_Comm_create(Comm, NewGroup, &NewComm):创建新的communicator。
这里需要注意的是:MPI_Group_incl()和``MPI_Comm_create()参数中,第一个参数Group和Comm,是父集的Group和Comm`。
上面三个都是Collective Calls!!!
MPI I/O
MPI_File_open()
collective call!!

使用MPI_File_open()而不是C语言库中的fopen()是因为:如果使用fopen(),多个进程请求同一个文件的时候,会造成错误(系统为了防止同时rewrite的问题,禁止同一个文件同时被打开多次)。
Read & Write
Collective I/O
对小I/O比较好
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vJBJmtnv-1572696279713)(/Users/panyumingzhi/Documents/asc/MPI编程/MPI_1/截屏2019-11-02下午7.29.06.png)]](https://i-blog.csdnimg.cn/blog_migrate/43bc8132074c2fcd830c9be0de4edab9.png)
Independent I/O
对大I/O较好

MPI-IO API

小小的吐槽:最近在看台湾人的网课,一直程式程式的,搞得我有点被带偏Orz。打出来的是程序,实际上心里想的是程式哈哈哈,口音真是一个奇怪的东西。*
 小小的吐槽:最近在看台湾人的网课,一直程式程式的,搞得我有点被带偏Orz。打出来的是程序,实际上心里想的是程式哈哈哈,口音真是一个奇怪的东西。
更多推荐
 



所有评论(0)