我的平行計算之路(二)MPI點對點通訊MPI_Send和MPI_Recv
學習了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就好。
用號程序呼叫
MPI_Send(send_buf_p,send_buf_sz,send_type,dest,send_tag,send_comm)
傳送的訊息能被號程序呼叫
MPI_Recv(recv_buf_p,recv_buf_sz,recv_type,src,recv_tag,recv_comm)
函式接收到,需要滿足:
- send_comm==recv_comm
- send_tag==recv_tag
- dest==r && c==q
若要成功接收,還需要保證recv_buf_szsend_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