学习了MPI的点对点技术后,来博客记录一下。先上完整地代码:
#include<bits/stdc++.h> #include<mpi.h> using namespace std; int comm_sz=0; int my_rank=0; int arra[]={1,2,3,4,5},arrb[]={10,9,8,7,6},arrc[]={0,0,0,0,0}; //获取输入,0进程从键盘读取,非0进程从0进程获取 bool get_input(double &a,double &b,int n) { if(my_rank==0){ //用户输入 cout<<" enter a,b,n"<<endl; cin>>a>>b>>n; } else{ //从进程0获取 MPI_Recv(&a,1,MPI_DOUBLE,0,0,MPI_COMM_WORLD,MPI_STATUS_IGNORE); MPI_Recv(&b,1,MPI_DOUBLE,0,0,MPI_COMM_WORLD,MPI_STATUS_IGNORE); MPI_Recv(&n,1,MPI_INT,0,0,MPI_COMM_WORLD,MPI_STATUS_IGNORE); } return true; } //加和数组a和数组b的对应项 int count_sum(int beg,int en) { int sum=0; for(int i=beg;i<en;i++){ sum += arra[i]+arrb[i]; } return sum; } int main(void) { int local_sum=0,total_sum=0; //local_sum为本进程使用,total_sum为计算风格进程的合使用 MPI_Init(NULL,NULL); MPI_Comm_rank(MPI_COMM_WORLD,&my_rank); MPI_Comm_size(MPI_COMM_WORLD,&comm_sz); //get_input(a,b,n); //测试时用的 int n = sizeof(arra)/sizeof(int); int jiange=n/(comm_sz-1); if(my_rank==0){ //进程0收集各个进程的结果,然后加和 cout<<"n: "<<n<<endl; for(int source=1;source<comm_sz;source++){ MPI_Recv(&local_sum,1,MPI_INT,source,0,MPI_COMM_WORLD,MPI_STATUS_IGNORE); total_sum += local_sum; } cout<<"total_sum: "<<total_sum<<endl; } else if(my_rank==(comm_sz-1)){ //最后一个进程用来处理剩下的数据 int rest=n-jiange*(comm_sz-2); int sum=count_sum(n-rest,n); MPI_Send(&sum,1,MPI_INT,0,0,MPI_COMM_WORLD); } else{ //非0,非最后进程来计算jiange内的数据和 int sum=count_sum(jiange*(my_rank-1),jiange*my_rank); MPI_Send(&sum,1,MPI_INT,0,0,MPI_COMM_WORLD); } MPI_Finalize(); return 0; }首先是几个函数接口: MPI系统初始化函数MPI_Init( )。原型如下:
int MPI_Init( int * argc_p, /* IN */ char*** argv_p /* OUT */ );参数**argc_p,*argv_p是指向argc,argv的指针,可以只设置成NULL*。返回值为错误码。初始化函数对系统进行了必要的初始化设置,例如,系统可能要为消息缓冲区分配存储空间,为进程指定进程号等。在程序调用MPI_Init( )前,不应该调用其他的MPI函数。 MPI使用完毕后,调用MPI_Finalize( )函数,将分配的资源释放,函数原型:
int MPI_Finalize(void);在调用MPI_Finalize( )函数后,就不要再调用MPI的函数了。下面介绍MPI_Comm_size()函数和MPI_Comm_size()函数。函数原型为:
int MPI_Comm_size( MPI_Comm comm, /* IN */ int* com_sz_p /* OUT */ ); int MPI_Comm_rank(MPI_Comm comm,int* my_rank_p)MPI_Comm_size()函数用来得到创建的进程数量,MPI_Comm_rank()函数用来得到当前进程编号。接下来就是点对点通信的重点MPI_Send()和MPI_Recv()函数。函数原型:
int MPI_Send( void* msg_buf_p, /* IN */ int msg_size, /* IN */ MPI_Datatype msg_type, /* IN */ int dest, /* IN */ int tag, /* IN */ MPI_Comm communicator /* IN */ );MPI_Send()函数中msg_buf_p、msg_size 和 msg_type定义了消息的内容,dest、tag 和 communicator定义了消息的目的地。
int MPI_Recv( void* msg_buf_p, /* OUT */ int buf_size, /* IN */ MPI_Datatype buf_type, /* IN */ int source, /* IN */ int tag, /* IN */ MPI_Comm communicator, /* IN */ MPI_Status* status_p /* OUT */ );MPI_Recv()函数中,msg_buf_p指向内存块,buf_size指定了内存中要存储对象的数量,buf_type说明了对象的类型。后面的三个参数用来识别参数,source用来指定接收从哪个进程发来的消息,tag需要与发送消息的参数tag相匹配,communicator需要与发送进程的通信子匹配。status_p一般不使用这个参数,赋予特殊的常量MPI_STATUS_IGNORE就好。 用 q q q号进程调用
MPI_Send(send_buf_p,send_buf_sz,send_type,dest,send_tag,send_comm)发送的消息能被 r r r号进程调用
MPI_Recv(recv_buf_p,recv_buf_sz,recv_type,src,recv_tag,recv_comm)函数接收到,需要满足:
send_comm==recv_commsend_tag==recv_tagdest==r && c==q若要成功接收,还需要保证recv_buf_sz ⩾ \geqslant ⩾send_buf_sz 接下来,讲一下程序的思路。每个进程被创建后,获取自己的进程号my_rank和进程总数comm_sz,然后计算非0进程需要计算的平均个数jiange。 非0号进程根据jiange和my_rank找到自己计算的区间,然后计算每一位对应的数组a和数组b的和,最后将结果用MPI_Send()函数发送给0号进程。 0号进程通过MPI_Recv()函数接收其他各个进程的结果,将各个结果加起来,计算一个总和。 其中,比较特殊的是最后一个非0进程,因为会存在计算量不能被进程平均分的情况,例如5个计算量,被2个非0进程分配。所以,最后一个进程就用来将剩下没有被分配到的计算量给计算了。 参考文献
[1] (美)Pacheco.An Introduction to Parallel Programming[M].邓倩妮等译.北京:机械工业出版社,2012.8