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 <stdio.h>
#include "mpi.h"
#include <stdlib.h>
#include <math.h>
#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(M<K){
int c=a;
a=b;
b=c;
}
if(comm_sz!=a*b || a>M || 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<M;i++){
for(int j=0;j<N;j++)
if(i<saveM)
Matrix_A[i*N+j]=i+1;
else
Matrix_A[i*N+j]=0;
for(int p=0;p<K;p++)
Matrix_C[i*K+p]=1;
}
for(int i=0;i<N;i++){
for(int j=0;j<K;j++)
if(j<saveK)
Matrix_B[i*K+j]=j+1;
else
Matrix_B[i*K+j]=0;
} // 输出A,B,C
//Matrix_print(Matrix_A,saveM,N);
//Matrix_print2(Matrix_B,N,saveK,K);
//Matrix_print2(Matrix_C,saveM,saveK,K);
printf("a=%d b=%d row=%d col=%d\n",a,b,each_row,each_col); start=MPI_Wtime();
// 主进程计算第1块
for(int i=0;i<each_row;i++){
for(int j=0;j<each_col;j++){
double temp=0;
for(int p=0;p<N;p++){
temp+=Matrix_A[i*N+p]*Matrix_B[p*K+j];
}
result_Matrix[i*K+j]=alpha*temp+beta*Matrix_C[i*K+j];
}
}
// 向每个进程发送其它块数据
for(int i=1;i<comm_sz;i++){
int beginRow=(i/b)*each_row; // 每个块的行列起始位置(坐标/偏移量)
int beginCol=(i%b)*each_col;
MPI_Send(Matrix_A+beginRow*N,each_row*N,MPI_DOUBLE,i,1,MPI_COMM_WORLD);
for(int j=0;j<N;j++)
MPI_Send(Matrix_B+j*K+beginCol,each_col,MPI_DOUBLE,i,j+2,MPI_COMM_WORLD);
for(int p=0;p<each_row;p++)
MPI_Send(Matrix_C+(beginRow+p)*K+beginCol,each_col,MPI_DOUBLE,i,p+2+N,MPI_COMM_WORLD);
} // 接收从进程的计算结果
for(int i=1;i<comm_sz;i++){
int beginRow=(i/b)*each_row;
int endRow=beginRow+each_row;
int beginCol=(i%b)*each_col;
for(int j=beginRow;j<endRow;j++)
MPI_Recv(result_Matrix+j*K+beginCol,each_col,MPI_DOUBLE,i,j-beginRow+2+N+each_row,MPI_COMM_WORLD,&status);
} //Matrix_print2(result_Matrix,saveM,saveK,K);
stop=MPI_Wtime();
printf("rank:%d time:%lfs\n",my_rank,stop-start); free(Matrix_A);
free(Matrix_B);
free(Matrix_C);
free(result_Matrix);
}
else {
double *buffer_A,*buffer_B,*buffer_C,*ans;
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*each_col*sizeof(double)); // C的均分行的数据 //接收A行B列的数据
MPI_Recv(buffer_A,each_row*N,MPI_DOUBLE,0,1,MPI_COMM_WORLD,&status);
for(int j=0;j<N;j++){
MPI_Recv(buffer_B+j*each_col,each_col,MPI_DOUBLE,0,j+2,MPI_COMM_WORLD,&status);
}
for(int p=0;p<each_row;p++){
MPI_Recv(buffer_C+p*each_col,each_col,MPI_DOUBLE,0,p+2+N,MPI_COMM_WORLD,&status);
}
//计算乘积结果,并将结果发送给主进程
for(int i=0;i<each_row;i++){
for(int j=0;j<each_col;j++){
double temp=0;
for(int p=0;p<N;p++){
temp+=buffer_A[i*N+p]*buffer_B[p*each_col+j];
}
buffer_C[i*each_col+j]=alpha*temp+beta*buffer_C[i*each_col+j];
}
}
//matMulti(buffer_A,buffer_B,buffer_C,each_row,N,each_col,alpha,beta); for(int j=0;j<each_row;j++){
MPI_Send(buffer_C+j*each_col,each_col,MPI_DOUBLE,0,j+2+N+each_row,MPI_COMM_WORLD);
}
free(buffer_A);
free(buffer_B);
free(buffer_C);
}
MPI_Finalize();
return 0;
}

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

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 <stdio.h>
#include "mpi.h"
#include <stdlib.h>
#include <math.h>
#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<M;i++){
for(int j=0;j<N;j++)
if(i<saveM)
Matrix_A[i*N+j]=j-i;
else
Matrix_A[i*N+j]=0;
for(int p=0;p<K;p++)
Matrix_C[i*K+p]=i*p;
}
for(int i=0;i<N;i++){
for(int j=0;j<K;j++)
if(j<saveK)
Matrix_B[i*K+j]=j+i;
else
Matrix_B[i*K+j]=0;
}
// 输出A,B,C
//Matrix_print(Matrix_A,saveM,N);
//Matrix_print2(Matrix_B,N,saveK,K);
//Matrix_print2(Matrix_C,saveM,saveK,K);
printf("row=%d col=%d\n",each_row,each_col); start=MPI_Wtime();
// 0进程存储第1块 B0数据
for(int i=0;i<N;i++)
for(int j=0;j<each_col;j++)
buffer_B[i*each_col+j]=Matrix_B[i*K+j];
// 发送B1 B2 ... B(comm_sz-1)列数据
for(int p=1;p<comm_sz;p++){
int beginCol=(p%comm_sz)*each_col; // 每个块的列起始位置(坐标/偏移量)
for(int i=0;i<N;i++)
MPI_Send(Matrix_B+i*K+beginCol,each_col,MPI_DOUBLE,p,i,MPI_COMM_WORLD);
} free(Matrix_B);
}
// 分发A0 A1 A2 ... A(comm_sz)行数据
MPI_Scatter(Matrix_A,each_row*N,MPI_DOUBLE,buffer_A,each_row*N,MPI_DOUBLE,0,MPI_COMM_WORLD);
MPI_Scatter(Matrix_C,each_row*K,MPI_DOUBLE,buffer_C,each_row*K,MPI_DOUBLE,0,MPI_COMM_WORLD);
if(my_rank==0){
free(Matrix_A);
free(Matrix_C);
}
// 接收B列数据
if(my_rank!=0){
for(int i=0;i<N;i++)
MPI_Recv(buffer_B+i*each_col,each_col,MPI_DOUBLE,0,i,MPI_COMM_WORLD,&status);
} //交换(comm_sz-1)次数据B 计算乘积结果
for(int k=0;k<comm_sz;k++){
//int beginRow=my_rank*each_row; // 每个块的行列起始位置(坐标/偏移量)
int beginCol=((k+my_rank)%comm_sz)*each_col;
for(int i=0;i<each_row;i++){
for(int j=0;j<each_col;j++){
double temp=0;
for(int p=0;p<N;p++){
temp+=buffer_A[i*N+p]*buffer_B[p*each_col+j];
}
ans[i*K+beginCol+j]=alpha*temp+beta*buffer_C[i*K+beginCol+j];
}
}
int dest=(my_rank-1+comm_sz)%comm_sz; // 向上一个进程发送数据块B
int src=(my_rank+1)%comm_sz; // 接收位置
MPI_Sendrecv_replace(buffer_B,N*each_col,MPI_DOUBLE,dest,N,src,N,MPI_COMM_WORLD,&status);
}
// 结果聚集
MPI_Gather(ans,each_row*K,MPI_DOUBLE,result_Matrix,each_row*K,MPI_DOUBLE,0,MPI_COMM_WORLD);
if(my_rank==0){
//Matrix_print2(result_Matrix,saveM,saveK,K);
stop=MPI_Wtime();
double E=(double)(5.957/(stop-start))/comm_sz;
printf("time:%lfs 并行效率:%.2lf%\n",stop-start,100*E);
}
free(buffer_A);
free(buffer_B);
free(buffer_C);
free(ans);
free(result_Matrix); MPI_Finalize();
return 0;
}

  

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

  1. Oracle学习笔记三 SQL命令

    SQL简介 SQL 支持下列类别的命令: 1.数据定义语言(DDL) 2.数据操纵语言(DML) 3.事务控制语言(TCL) 4.数据控制语言(DCL)  

  2. [Firefly引擎][学习笔记三][已完结]所需模块封装

    原地址:http://www.9miao.com/question-15-54671.html 学习笔记一传送门学习笔记二传送门 学习笔记三导读:        笔记三主要就是各个模块的封装了,这里贴 ...

  3. JSP学习笔记(三):简单的Tomcat Web服务器

    注意:每次对Tomcat配置文件进行修改后,必须重启Tomcat 在E盘的DATA文件夹中创建TomcatDemo文件夹,并将Tomcat安装路径下的webapps/ROOT中的WEB-INF文件夹复 ...

  4. java之jvm学习笔记三(Class文件检验器)

    java之jvm学习笔记三(Class文件检验器) 前面的学习我们知道了class文件被类装载器所装载,但是在装载class文件之前或之后,class文件实际上还需要被校验,这就是今天的学习主题,cl ...

  5. VSTO学习笔记(三) 开发Office 2010 64位COM加载项

    原文:VSTO学习笔记(三) 开发Office 2010 64位COM加载项 一.加载项简介 Office提供了多种用于扩展Office应用程序功能的模式,常见的有: 1.Office 自动化程序(A ...

  6. Java IO学习笔记三

    Java IO学习笔记三 在整个IO包中,实际上就是分为字节流和字符流,但是除了这两个流之外,还存在了一组字节流-字符流的转换类. OutputStreamWriter:是Writer的子类,将输出的 ...

  7. NumPy学习笔记 三 股票价格

    NumPy学习笔记 三 股票价格 <NumPy学习笔记>系列将记录学习NumPy过程中的动手笔记,前期的参考书是<Python数据分析基础教程 NumPy学习指南>第二版.&l ...

  8. Learning ROS for Robotics Programming Second Edition学习笔记(三) 补充 hector_slam

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...

  9. Learning ROS for Robotics Programming Second Edition学习笔记(三) indigo rplidar rviz slam

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...

随机推荐

  1. ”只用 1 分钟“ - 超简极速 Apk 签名 & 多渠道打包神器

    众所周知,渠道包作为当下国内 Android 应用市场常见的分发方式,当 APP 和后台交互或进行数据上报时,会带上各自的 channel 渠道信息,以此方便企业 & 开发者统计 APP 在各 ...

  2. 【SpringBoot】YAML 配置文件

    博客主页:准Java全栈开发工程师 00年出生,即将进入职场闯荡,目标赚钱,可能会有人觉得我格局小.觉得俗,但不得不承认这个世界已经不再是以一条线来分割的平面,而是围绕财富旋转的球面,成为有钱人不是为 ...

  3. lvm逻辑卷创建及使用

    创建逻辑卷 pvcreate /dev/md0 pvs 查看创建的pv组 pvdisplay /dev/md0 查看磁盘详细信息 添加vg组: 创建vg组: vgcreate vg1 /dev/md0 ...

  4. sed基本使用

    1. 删除由空格组成的空白行 sed '/^ *$/d' test.txt sed '/[ ][ ]/d' test.txt 2. 删除空白行 sed '/^[[:space:]]*$/d' test ...

  5. bat-命令行安装软件

    批处理 执行的两种方式 1.直接右键以管理员身份运行 2.在管理员身份的cmd窗口中 .\xxx.bat 执行 区别 第一种方式 当前cmd默认路径为 C:\windows\system32 第二种方 ...

  6. Tapdata 与阿里云 PolarDB 开源数据库社区联合共建开放数据技术生态

      近日,阿里云 PolarDB 开源数据库社区宣布将与 Tapdata 联合共建开放数据技术生态.在此之际,一直专注实时数据服务平台的 Tapdata ,也宣布开源其数据源开发框架--PDK(Plu ...

  7. HashMap源码深度剖析,手把手带你分析每一行代码,包会!!!

    HashMap源码深度剖析,手把手带你分析每一行代码! 在前面的两篇文章哈希表的原理和200行代码带你写自己的HashMap(如果你阅读这篇文章感觉有点困难,可以先阅读这两篇文章)当中我们仔细谈到了哈 ...

  8. APISpace 周公解梦API接口 免费好用

    <周公解梦>,是根据人的梦来卜吉凶的一本解梦书籍,它对人的七类梦境进行解述.   周公解梦API,周公解梦大全,周公解梦查询,免费周公解梦.   APISpace 有很多免费通用的API接 ...

  9. 5-5 SpringGateway 网关

    SpringGateway 网关 奈非框架简介 早期(2020年前)奈非提供的微服务组件和框架受到了很多开发者的欢迎 这些框架和Spring Cloud Alibaba的对应关系我们要知道 Nacos ...

  10. Eslint 项目笔记

    1.代码下一行不要验证报错 代码的上一行打上注释 <--eslint-disable-next-line-->