1.准備
使用MPI做並行計算時,根據程序的具體要求,可按任務進行分配或數據進行分配。根據矩陣乘積的特點,這里按數據進行分配,即每個計算機節點計算不同的數據,由於矩陣數據的特點,這里按行進行數據分塊。因為本人使用的是C語言,數組在C語言的表示下行間數據地址是連續的(注:若是Fortran語言,則列是連續的)。
2.mpi程序的框架
mpi程序運行是靠輸入dos命令執行的,因此,mpi程序一般都在main函數內,也即程序入口函數中。一般都會有MPI_Init、MPI_Comm_rank、MPI_Comm_size、MPI_Finalize這四個函數。用法如下:
記得要加上mpi.lib
1 #include “mpi.h” 2 3 int main(int argc,char *argv[]) 4 { 5 int myid,numprocs; 6 MPI_Init(&argc,&argv);//MPI Initialize 7 MPI_Comm_rank(MPI_COMM_WORLD,&myid);//獲得當前進程號 8 MPI_Comm_size(MPI_COMM_WORLD,&numprocs);//獲得進程個數 9 //mpi計算過程 10 MPI_Finalize();//結束 11 }
3.矩陣乘法
矩陣乘法在於對矩陣進行分塊,然后交由各進程執行,最后將計算結果傳遞給主進程。
假設是M*N,計算前,將矩陣N發送給所有從進程,然后將矩陣M分塊,將M中數據按行分給各從進程,在從進程中計算M中部分行數據和N的乘積,最后將結果發送給主進程。這里為了方便,有多少進程,就將M分了多少塊,除最后一塊外的其他數據塊大小都相等,最后一塊是剩下的數據,大小大於等於其他數據塊大小,因為矩陣行數不一定整除進程數。最后一塊數據在主進程中計算,其他的在從進程中計算。
定義兩個矩陣M和N,N所有進程都需要,M可以只在主進程中定義。其他的變量視主進程和從進程需要按要求定義在合適的位置。
代碼如下,包括矩陣初始化,數據傳遞,矩陣乘積計算等。
1 void matgen(float* a,int Width); 2 //產生矩陣 3 void matgen(float* a,int Width) 4 { 5 int i,j; 6 for (i=0;i<Width;i++) 7 { 8 for (j=0;j<Width;j++) 9 { 10 //a[i*Width+j]=(float)rand()/RAND_MAX + (float)rand()/(RAND_MAX*RAND_MAX); //產生矩陣,矩陣中元素0~1 11 a[i*Width+j]=1.00; 12 } 13 } 14 } 15 16 void main(int argc,char *argv[]) 17 { 18 float *M,*N,*P,*buffer,*ans; 19 int Width=1000; 20 int myid,numprocs; 21 MPI_Status status; 22 23 MPI_Init(&argc,&argv);//MPI Initialize 24 MPI_Comm_rank(MPI_COMM_WORLD,&myid);//獲得當前進程號 25 MPI_Comm_size(MPI_COMM_WORLD,&numprocs);//獲得進程個數 26 27 int line = Width/numprocs;//將數據分為(進程數)個塊,主進程也要處理數據 28 M = (float*)malloc(sizeof(float)*Width*Width); 29 N = (float*)malloc(sizeof(float)*Width*Width); 30 P = (float*)malloc(sizeof(float)*Width*Width); 31 //緩存大小大於等於要處理的數據大小,大於時只需關注實際數據那部分 32 buffer = (float*)malloc(sizeof(float)*Width*line);//數據分組大小 33 ans = (float*)malloc(sizeof(float)*Width*line);//保存數據塊結算的結果 34 35 //主進程對矩陣賦初值,並將矩陣N廣播到各進程,將矩陣M分組廣播到各進程 36 if (myid==0) 37 { 38 //矩陣賦初值 39 matgen(M,Width); 40 matgen(N,Width); 41 //將矩陣N發送給其他從進程 42 //MPI_Bcast(N,Width*Width,MPI_FLOAT,0,MPI_COMM_WORLD); 43 for (int i=1;i<numprocs;i++) 44 { 45 MPI_Send(N,Width*Width,MPI_FLOAT,i,0,MPI_COMM_WORLD); 46 } 47 //依次將M的各行發送給各從進程 48 for (int m=1;m<numprocs;m++) 49 { 50 /* 51 //按行分組,得到buffer,發送給從進程 52 for (int i=(m-1)*line;i<m*line;i++) 53 { 54 for (int j=0;j<Width;j++) 55 { 56 buffer[(i-(m-1)*line)*Width+j]=M[i*Width+j]; 57 } 58 } 59 //經數據buffer發送到進程m 60 MPI_Send(buffer,line*Width,MPI_FLOAT,m,m,MPI_COMM_WORLD); 61 */ 62 MPI_Send(M+(m-1)*line*Width,Width*line,MPI_FLOAT,m,1,MPI_COMM_WORLD); 63 } 64 //接收從進程計算的結果 65 for (int k=1;k<numprocs;k++) 66 { 67 MPI_Recv(ans,line*Width,MPI_FLOAT,k,3,MPI_COMM_WORLD,&status); 68 //將結果傳遞給數組P 69 for (int i=0;i<line;i++) 70 { 71 for (int j=0;j<Width;j++) 72 { 73 P[((k-1)*line+i)*Width+j]=ans[i*Width+j]; 74 } 75 } 76 } 77 //計算M剩下的數據 78 for (int i=(numprocs-1)*line;i<Width;i++) 79 { 80 for (int j=0;j<Width;j++) 81 { 82 float temp=0.0; 83 for (int k=0;k<Width;k++) 84 temp += M[i*Width+k]*N[k*Width+j]; 85 P[i*Width+j]=temp; 86 } 87 } 88 //測試結果,計算一行的和 89 float sum1=0; 90 float sum2=0; 91 for (int i=0;i<Width;i++) 92 { 93 sum1 += M[i]; 94 sum2 += P[600*Width+i]; 95 } 96 printf("sum1=%f sum2=%f\n",sum1,sum2); 97 //統計時間 98 double clockEnd = (double)clock(); 99 printf("myid:%d time:%.2fs\n",myid,(clockEnd-clockStart)/1000);//結果測試 100 } 101 102 //其他進程接收數據,計算結果后,發送給主進程 103 else 104 { 105 //接收廣播的數據(矩陣N) 106 //MPI_Bcast(N,Width*Width,MPI_FLOAT,0,MPI_COMM_WORLD); 107 MPI_Recv(N,Width*Width,MPI_FLOAT,0,0,MPI_COMM_WORLD,&status); 108 109 MPI_Recv(buffer,Width*line,MPI_FLOAT,0,1,MPI_COMM_WORLD,&status); 110 //計算乘積結果,並將結果發送給主進程 111 for (int i=0;i<line;i++) 112 { 113 for (int j=0;j<Width;j++) 114 { 115 float temp=0.0; 116 for(int k=0;k<Width;k++) 117 temp += buffer[i*Width+k]*N[k*Width+j]; 118 ans[i*Width+j]=temp; 119 } 120 } 121 //將計算結果傳送給主進程 122 MPI_Send(ans,line*Width,MPI_FLOAT,0,3,MPI_COMM_WORLD); 123 } 124 125 MPI_Finalize();//結束 126 127 return 0; 128 }
可以對上述結果進行測試,測試時為了方便可以不用每次都在dos終端下輸入命令,建立一個.bat的批處理命令即可,方法如下:
新建一個文本文件,在里面加上執行命令,諸如:mpiexec -n 5 XXX.exe,然后將文件重命名為xxx.bat,雙擊該bat文件即可執行mpi程序,但是會看不到輸出結果,此方法在帶有GUI的程序時很有用,運行其他程序或要對運行參數進行修改時,只需要編輯bat文件即可。相對於在命令提示符下可謂方便許多。
對於上面的程序其實還有優化的地方,仔細分析,可知,進程中需要的數據如下:
主進程:M、N、P(存儲結果)、line
從進程:N、buffer(存儲M分塊的數據)、ans(存儲計算結果)、line(計算M的行數)
buffer直接接受M分塊的數據,主進程中矩陣P直接接受從進程ans傳遞的結果值。
如下:
1 for (int k=1;k<numprocs;k++) 2 {
MPI_Recv(P+(k-1)*line*Width,line*Width,MPI_FLOAT,k,3,MPI_COMM_WORLD,&status); 3 }
4.題外
除了該方法外,還有一種就是計算都在從進程中計算,主進程只負責數據的管理。這樣計算所花的時間要比上面方法的時間短,具體請自行驗證。