MPI程序编译和使用

c++中引入#include"mpi.h"引入mpi

编译,参数和g++类似

1
mpic++ -o hello ./hello.cpp

运行

1
mpirun -np 4 ./hello

指定四个进程运行hello程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include"mpi.h"
#include<iostream>

int main(int argc,char* argv[]){
// std::cout<<"argc:"<<argc<<std::endl;
// for(int i=0;i<=argc;i++){
// std::cout<<"argv"<<i<<": "<<*(argv+i)<<std::endl;
// }
MPI::Init(argc,argv);
int numP=MPI::COMM_WORLD.Get_size();
int myID=MPI::COMM_WORLD.Get_rank();

std::cout<<"my id :"<<myID<<" total:"<<numP<<std::endl;
MPI::Finalize();
}

使用mpirun运行程序与直接运行程序argc和argv不变。进程数4没有放在argv里

1
2
MPI::Init(argc,argv);
MPI::Finalize();

表示MPI的初始化和析构函数

1
2
int numP=MPI::COMM_WORLD.Get_size();
int myID=MPI::COMM_WORLD.Get_rank();

get_size获取总共进程数量

get_rank获取当前进程id。进程id每个进程不一样,用于管理当前进程

id从0开始

点到点通信

非阻塞通信

send发送和recv接收函数

1
2
void Send(const void* buf, int count, const Datatype& datatype, int dest,int tag)
void Recv(void* buf, int count, const Datatype& datatype, int source, int tag)

其中datatype为MPI中提供的基本数据类型eg:MPI::INT MPI::FLOAT MPI::DOUBLE

Send和Recv为阻塞通信,Send发送的未被接收,或Recv未接受数据程序均会在此处暂停。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include"mpi.h"
#include<iostream>
#include <unistd.h>

int main(int argc,char* argv[]){

int pingCount=0;
int pongCount=0;

MPI::Init(argc,argv);
int numP=MPI::COMM_WORLD.Get_size();
int myID=MPI::COMM_WORLD.Get_rank();

int nextProc=(myID + 1)%numP;
int lastProc=myID - 1;
if(lastProc==-1)lastProc=lastProc + numP;

if(myID==0)MPI::COMM_WORLD.Send(&pingCount,1,MPI::INT,nextProc,0);
while(pingCount<10){
MPI::COMM_WORLD.Recv(&pingCount,1,MPI::INT,lastProc,0);
pingCount++;
std::cout<<"my id :"<<myID<<" pingCount:"<<pingCount<<std::endl;
sleep(2);
}


MPI::Finalize();
}

该程序为一个简单的令牌环在进程间发送ping来实现进程之间简单的通信A->B->C->D->A->B......如此循环

结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ mpirun -np 4 ./hello
my id :1 pingCount:1
my id :2 pingCount:2
my id :3 pingCount:3
my id :0 pingCount:4
my id :1 pingCount:5
my id :2 pingCount:6
my id :3 pingCount:7
my id :0 pingCount:8
my id :1 pingCount:9
my id :2 pingCount:10
my id :3 pingCount:11
my id :0 pingCount:12
my id :1 pingCount:13

循环到第十三次是由于第九次发送出消息,此时还在循环中,等待着消息走一圈回来后变成第十三次

如果不加sleep结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ mpirun -np 4 ./hello
my id :1 pingCount:1
my id :2 pingCount:2
my id :3 pingCount:3
my id :0 pingCount:4
my id :0 pingCount:8
my id :0 pingCount:12
my id :1 pingCount:5
my id :1 pingCount:9
my id :1 pingCount:13
my id :2 pingCount:6
my id :2 pingCount:10
my id :3 pingCount:7
my id :3 pingCount:11

打印顺序是乱序,猜测是由于进程间通信速度快于cout打印速度导致出现插队的现象

非阻塞通信

1
2
MPI::Request Isend(const void* buf, int count, const Datatype& datatype, int dest,int tag)
MPI::Request Irecv(void* buf, int count, const Datatype& datatype, int source, int tag)

非阻塞通信立即返回,不阻塞后续程序运行

Wait()函数会等待之前所有的通信完成

Test()函数会返回布尔值判断是否结束,不阻塞程序运行

广播机制

使用广播可以让一个进程中的数据发送到所有进程中去

1
void Bcast(void *buffer,int count, const MPI::DataType& datatype,int root)

Bcast本身就包括发送和接收两个功能,root进程负责发送,其他进程负责接收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include"mpi.h"
#include<iostream>
#include <unistd.h>

int main(int argc,char* argv[]){

int mes=12345;

MPI::Init(argc,argv);
int numP=MPI::COMM_WORLD.Get_size();
int myID=MPI::COMM_WORLD.Get_rank();

std::cout<<"my id :"<<myID<<" mes:"<<mes<<std::endl;
if(myID == 0)mes=54321;
int root =0;

MPI::COMM_WORLD.Bcast(&mes,1,MPI::INT,root);
std::cout<<"my id :"<<myID<<" mes:"<<mes<<std::endl;
//sleep(2);

MPI::Finalize();
}
1
2
3
4
5
6
7
8
9
$ mpirun -np 4 ./hello
my id :0 mes:12345
my id :0 mes:54321
my id :1 mes:12345
my id :2 mes:12345
my id :2 mes:54321
my id :3 mes:12345
my id :1 mes:54321
my id :3 mes:54321

数据分配与收集

数据分配

1
Scatter(const void* sendbuf, int sendcount, const MPI::Datatype& sendtype, void* recvbuf, int recvcount, const MPI::Datatype& recvtype, int root)

Scatter会把root进程内存中的数据均分成几份,发送到其他进程中去

此处sendcount,第二个参数代表这每个进程接收的数量,而不是根进程发送数据一共的数量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include"mpi.h"
#include<iostream>
#include <unistd.h>

int main(int argc,char* argv[]){

int mes[10]={0,1,2,3,4,5,6,7,8,9};
int mydata=-1;

MPI::Init(argc,argv);

int numP=MPI::COMM_WORLD.Get_size();
int myID=MPI::COMM_WORLD.Get_rank();
int root =0;

std::cout<<"my id :"<<myID<<" mydata:"<<mydata<<std::endl;
MPI::COMM_WORLD.Scatter(mes,1,MPI::INT,&mydata,1,MPI::INT,root);
std::cout<<"my id :"<<myID<<" mydata:"<<mydata<<std::endl;
//sleep(2);

MPI::Finalize();
}
1
2
3
4
5
6
7
8
9
$ mpirun -np 4 ./hello
my id :0 mydata:-1
my id :0 mydata:0
my id :1 mydata:-1
my id :1 mydata:1
my id :2 mydata:-1
my id :2 mydata:2
my id :3 mydata:-1
my id :3 mydata:3

该程序会把数组mes里面的数据发放到各个进程中去,Scatter自带计数器,每次发送完成后指针会往后偏移一个单位,避免重复发送元素。

多次实验下来,似乎接收是按照进程id顺序依次进行接收。

数据收集

1
Gather(const void* sendbuf,int sendcount, const MPI::Datatype& sendtype, void* recvbuf, int recvcount, const MPI::Datatype& recvtype, int root)

会把其他进程的数据收集起来统一发送到根进程去,和Scatter正好相反

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include"mpi.h"
#include<iostream>
#include <unistd.h>

int main(int argc,char* argv[]){

int mes[10]={-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
int mydata=-1;

MPI::Init(argc,argv);
int numP=MPI::COMM_WORLD.Get_size();
int myID=MPI::COMM_WORLD.Get_rank();

if(myID==0){
for (int i=0;i<numP;i++)std::cout<<mes[i]<<" ";
std::cout<<std::endl;
}
mydata=myID+10;

int root =0;
std::cout<<"my id :"<<myID<<" mydata:"<<mydata<<std::endl;
MPI::COMM_WORLD.Gather(&mydata,1,MPI::INT,mes,1,MPI::INT,root);
std::cout<<"my id :"<<myID<<" mydata:"<<mydata<<std::endl;

if(myID==0){
for (int i=0;i<numP;i++)std::cout<<mes[i]<<" ";
std::cout<<std::endl;
}
//sleep(2);

MPI::Finalize();
}
1
2
3
4
5
6
7
8
9
10
11
$ mpirun -np 4 ./hello
my id :1 mydata:11
my id :2 mydata:12
-1 -1 -1 -1
my id :1 mydata:11
my id :3 mydata:13
my id :3 mydata:13
my id :0 mydata:10
my id :0 mydata:10
10 11 12 13
my id :2 mydata:12

几次实验下来,发送似乎也是按照ID号先后进行发送