1. 程式人生 > >我的平行計算之路(二)MPI點對點通訊MPI_Send和MPI_Recv

我的平行計算之路(二)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_sizemsg_type定義了訊息的內容,dest、tagcommunicator定義了訊息的目的地。

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就好。
  用qq號程序呼叫

MPI_Send(send_buf_p,send_buf_sz,send_type,dest,send_tag,send_comm)

傳送的訊息能被rr號程序呼叫

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_sz\geqslantsend_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