MPI学习笔记(三):矩阵相乘的分块并行(行列划分法)


mpi矩阵乘法:C=αAB+βC

一、主从模式的行列划分并行法

1、实现方法

将可用于计算的进程数comm_sz分解为a*b,然后将矩阵A全体行划分为a个部分,将矩阵B全体列划分为b个部分,从而将整个结果矩阵划分为size相同的comm_sz个块。每个子进程负责计算最终结果的一块,只需要接收A对应范围的行和B对应范围的列,而不需要把整个矩阵传过去。主进程负责分发和汇总结果。

进程数comm_sz分解为a*b的方法:
int a=comm_sz/(int)sqrt(comm_sz);
int b=(int)sqrt(comm_sz);
例如comm_sz=12时,a=3,b=4。
但当comm_sz=13时,a=3,b=4,此时进程总数comm_sz不等于a*b,这样就会导致有多余的进程不参与运算。

2、示例

当总进程数为comm_sz=6时计算以下A*B矩阵,其中矩阵A划分了a=3块,B矩阵划分了b=2块。

 
第一个进程计算C00=A0B0,第二个进程计算C01=A0B1,以此类推。那么如何保证传输到各个进程矩阵A的行数据和矩阵B的列数据是相对应的。

3、对进程总数没有公约数的处理

方法1:当有多余的进程不参与运算时,使用MPI_ABORT终止该进程。
方法2:在MPI开始时加一个判断语句来终止MPI运行,让用户重新选择进程总数。

4、完整代码

#include 
#include "mpi.h"
#include 
#include 
#include "dgemm_1.h"
/*** 主从模式 行列划分 ***/
int main(int argc, char **argv)
{
   int M=2000,N=3000,K=2500; // 矩阵维度
   int my_rank,comm_sz;
   double start, stop; //计时时间
   double alpha=2,beta=2; // 系数C=aA*B+bC
   MPI_Status status;

   MPI_Init(&argc,&argv);
   MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
   MPI_Comm_rank(MPI_COMM_WORLD,&my_rank);

   int a=comm_sz/(int)sqrt(comm_sz); // A矩阵行分多少块
   int b=(int)sqrt(comm_sz); // B矩阵列分多少块
   if(MM || b>K){
      if(my_rank==0)
         printf("error process:%d\n",comm_sz);
      MPI_Finalize();
      return 0;
   }

   int saveM=M; // 为了使行数据平均分配每一个进程
   if(M%a!=0){
      M-=M%a;
      M+=a;
   }
   int saveK=K;// 为了使列数据平均分配每一个进程
   if(K%b!=0){
      K-=K%b;
      K+=b;
   }
   int each_row=M/a; // 矩阵A每块分多少行数据
   int each_col=K/b; // 矩阵B每列分多少列数据

   // 给矩阵A B,C赋值
   if(my_rank==0){
      double *Matrix_A,*Matrix_B,*Matrix_C,*result_Matrix;
      Matrix_A=(double*)malloc(M*N*sizeof(double));
      Matrix_B=(double*)malloc(N*K*sizeof(double));
      Matrix_C=(double*)malloc(M*K*sizeof(double));
      result_Matrix=(double*)malloc(M*K*sizeof(double)); // 保存数据计算结果

      for(int i=0;i

二、对等模式的行列划分并行法

1、实现方法

i、将可用于计算的进程数为comm_sz,然后将矩阵A全体行划分为comm_sz个部分,将矩阵B全体列划分为comm_sz个部分,从而将整个结果矩阵C划分为size相同的comm_sz*comm_sz个块。每个子进程负责计算最终结果的一块,只需要接收A对应范围的行和B对应范围的列,而不需要把整个矩阵传过去。主进程负责分发和汇总结果。
ii、主从模式里是每个进程所需的块数据都是由主进程提供的,而对等模式里每个进程里只存储一块数据,所需的其它块数据要从其它进程交换。
iii、之后开始计算后所需的Bi数据从下一个进程接收,当前进程的Bi数据发向前一个进程,每个进程计算结果矩阵的一行的块矩阵。

2、实现步骤

(1)将Ai,Bi和Cij(j=0,1,...,p-1)存放在第i个处理器中(这样处理的方式是为了使得数据在处理器中不重复);
(2)Pi负责计算Cij(j=0,1,...,p-1);
(3)由于使用P个处理器,每次每个处理器只计算一个Cij,故计算出整个C需要p次完成。
(4)Cij计算是按对角线进行的。

3、完整代码

#include 
#include "mpi.h"
#include 
#include 
#include "dgemm_1.h"
/*** 对等模式 行列划分 ***/
int main(int argc, char **argv)
{
   int M=10000,N=10000,K=10000; // 矩阵维度
   int my_rank,comm_sz;
   double start, stop; //计时时间
   double alpha=2,beta=2; // 系数C=aA*B+bC
   MPI_Status status;

   MPI_Init(&argc,&argv);
   MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
   MPI_Comm_rank(MPI_COMM_WORLD,&my_rank);

   if(comm_sz>M || comm_sz>K){
      if(my_rank==0)
         printf("error process:%d\n",comm_sz);
      MPI_Finalize();
      return 0;
   }

   int saveM=M; // 为了使行数据平均分配每一个进程
   if(M%comm_sz!=0){
      M-=M%comm_sz;
      M+=comm_sz;
   }
   int saveK=K;// 为了使列数据平均分配每一个进程
   if(K%comm_sz!=0){
      K-=K%comm_sz;
      K+=comm_sz;
   }
   int each_row=M/comm_sz; // 矩阵A每块分多少行数据
   int each_col=K/comm_sz; // 矩阵B每列分多少列数据
   double *Matrix_A,*Matrix_C,*buffer_A,*buffer_B,*buffer_C,*ans,*result_Matrix;
   buffer_A=(double*)malloc(each_row*N*sizeof(double)); // A的块数据
   buffer_B=(double*)malloc(N*each_col*sizeof(double)); // B的块数据
   buffer_C=(double*)malloc(each_row*K*sizeof(double)); // C的块数据
   ans=(double*)malloc(each_row*K*sizeof(double)); // 临时存储每块的结果数据
   result_Matrix=(double*)malloc(M*K*sizeof(double)); // 保存数据计算结果

   if(my_rank==0){
      double *Matrix_B;
      Matrix_A=(double*)malloc(M*N*sizeof(double));
      Matrix_B=(double*)malloc(N*K*sizeof(double));
      Matrix_C=(double*)malloc(M*K*sizeof(double));
      // 给矩阵A B,C赋值
      for(int i=0;i

  

 
 

mpi