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.題外
除了該方法外,還有一種就是計算都在從進程中計算,主進程只負責數據的管理。這樣計算所花的時間要比上面方法的時間短,具體請自行驗證。
