MPI用於矩陣乘積示例


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.題外

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM