集合通信对比点对点通信来说,确实要方便许多,基本上不需要考虑各个进程该怎么发送,该怎么接收的问题,只要是使用一个通信子,函数接口自己就完成了。从进程的角度考虑,集合通信在分配任务量的时候更加平均,使各个进程的工作量更加相同,减少让一个进程死干活,其他进程在旁边看的情况。下面还是先上完整代码:
#include<bits/stdc++.h> #include<mpi.h> using namespace std; const int maxn=10; //计算x,y两个数组对应位的和,存入第三个数组z bool Parallel_vector_sum( double local_x[], //加数组 double local_y[], //被加数组 double local_z[], //存储和的数组 int local_n //加的个数 ) { for(int local_i=0;local_i<local_n;local_i++){ local_z[local_i] = local_x[local_i]+local_y[local_i]; } return true; } //从键盘读取数组 bool Read_vector( double local_a[], //存储数据的数组,OUT int local_n, //每个进程需要分配的数量 int n, //读取的总个数 string vec_name, //数组的名字 int my_rank, //进程号 MPI_Comm comm //通信子 ) { double* a = NULL; if(my_rank==0){ //输入数组 a = new double[n]; cout<<"Enter the vector "<<vec_name<<endl; for(int i=0;i<n;i++){ cin>>a[i]; } //数据分发 MPI_Scatter(a,local_n,MPI_DOUBLE,local_a,local_n,MPI_DOUBLE,0,comm); delete [] a; } else{ //接收数据 MPI_Scatter(a,local_n,MPI_DOUBLE,local_a,local_n,MPI_DOUBLE,0,comm); } return true; } bool Print_vector( double local_b[], //需要输出的数组 int local_n, //每个进程需要输出的数量 int n, //总数量 string title, //数组的名字 int my_rank, //进程号 MPI_Comm comm //通信子 ) { double* b=NULL; if(my_rank==0){ //接收数组 b = new double[n]; MPI_Gather(local_b,local_n,MPI_DOUBLE,b,local_n,MPI_DOUBLE,0,comm); //打印数组 cout<<title<<endl; for(int i=0;i<n;i++){ cout<<b[i]<<" "; } cout<<endl; delete [] b; } else{ //发送数组 MPI_Gather(local_b,local_n,MPI_DOUBLE,b,local_n,MPI_DOUBLE,0,comm); } return true; } int main(void) { int my_rank=0; int comm_sz=0; MPI_Init(NULL,NULL); MPI_Comm_rank(MPI_COMM_WORLD,&my_rank); MPI_Comm_size(MPI_COMM_WORLD,&comm_sz); streambuf * inbuf = cin.rdbuf((new ifstream("input"))->rdbuf());//重定向,OJ时将它注释掉 int local_n=maxn/comm_sz; double local_a[maxn],local_b[maxn],local_c[maxn]; //读取待相加的两个数组 Read_vector(local_a,local_n,maxn,"arryA",my_rank,MPI_COMM_WORLD); Read_vector(local_b,local_n,maxn,"arryB",my_rank,MPI_COMM_WORLD); //计算对应的和 Parallel_vector_sum(local_a,local_b,local_c,local_n); //打印结果数组 Print_vector(local_c,local_n,maxn,"arry",my_rank,MPI_COMM_WORLD); MPI_Finalize(); return 0; }首先介绍一下两个重要的函数接口MPI_Scatter()和MPI_Gather()。函数原型为:
int MPI_Scatter( void* send_buf_p, /* IN */ int send_count, /* IN */ MPI_Datatype send_type, /* IN */ void* recv_buf_p, /* OUT */ int recv_count, /* IN */ MPI_Datatype recv_type, /* IN */ int src_proc, /* IN */ MPI_Comm comm /* IN */ )MPI_Scatter()函数可以将分量发送给需要分量的其他进程,对于分发的进程src_proc而言,该函数用来分发数据;对于其他进程而言,该函数用来接收数据。其将send_buf_p指向的数据分成若干份,每份send_count个。然后将第一份交给0号进程,第二份交给1号进程…。在接收时,进程用本地的指针recv_buf_p来接收数据,接收数量为recv_count。一般情况下满足
send_count == recv_countsend_type == recv_type块划分方法通信子comm相同数据总数n能被进程数量comm_sz整除下面是MPI_Gather()函数原型:
int MPI_Gather( void* send_buf_p, /* IN */ int send_count, /* IN */ MPI_Datatype send_type, /* IN */ void* recv_buf_p, /* OUT */ int recv_count, /* IN */ MPI_Datatype recv_type, /* IN */ int dest_proc, /* IN */ MPI_Comm comm /* IN */ )MPI_Scatter()函数可以将分量发送给需要分量的其他进程,对于收集数据的进程dest_proc而言,该函数用来收集数据;对于其他进程而言,该函数用来发送数据。接收0号进程数据作为第一份存入recv_buf_p的第一个块中,接收1号进程数据作为第二份存入recv_buf_p的第一个块中…,接收每个进程recv_count个数据。在发送时,进程用本地的指针send_buf_p来发送数据,发送数量为recv_count。一般情况下满足条件与分发相同:
send_count == recv_countsend_type == recv_type块划分方法通信子comm相同数据总数n能被进程数量comm_sz整除然后讲解一下程序的思路。首先通过0号进程从键盘接收待加的a和b数组,然后将数据平均分发给各个进程。各个进程分别计算分发给自己的数据和,将对应的数据存入结果数组c中。然后调用打印函数,在0号进程将各个进程的结果拼接在一起,打印出来。 最后补充一个广播函数MPI_Bcast,用来将数据广播到各个进程,原型为
int MPI_Bcast( void* data_p, /* in/out */ int count, /* in */ MPI_Datatype datatype, /* in */ int source_proc, /* in */ MPI_Comm comm /* in */ )data_p既作为输入数据也作为输出数据,此外需要指出传递的数据量和数据类型,还需要指定数据源的进程号。所以原来的get_input 函数(参见 我的并行计算之路(二) 中完整代码里的get_input 函数)就可以改写成:
//获取输入,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; } MPI_Bcast(&a, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); MPI_Bcast(&b, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); MPI_Bcast(&n, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD); return true; }可见,利用广播函数后,程序会变得简洁,而且不需要考虑是发送还是接受进程,不用填写复杂的参数了。 参考文献
[1] (美)Pacheco.An Introduction to Parallel Programming[M].邓倩妮等译.北京:机械工业出版社,2012.8
