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

一、Cannon卡农算法基本介绍

1、二维矩阵串行乘法

两个n维方阵的乘法A×B=C的串行计算公式为:

下图是用图示来表示的这种计算规则:

2、二维块划分的思想

并行化二维矩阵乘法的一种思想是二维块划分方法,将p个进程(p为完全平方数)排列成sqrt(p)×sqrt(p)二维网格,然后将矩阵A、B、C都分成sqrt(p)×sqrt(p)块,均匀分布在网格上,矩阵第(i,j)个子块分别记为Aij、Bij、Cij,储在二维进程网格上坐标为(i,j)的进程Pij上。计算Cij时要将Aij(第i行进程上的所有A的子块)和Bij(第j列进程上的所有B的子块)都收集到进程Pij上,再计算Cij,公式可以表示为:

下图是用图示来表示的这种计算规则:

然而每一个进程都重复收集Aik和Bkj会造成空间的大量浪费,时间效率也比较低,于是有了更高效的Cannon算法,其表达式为:

3、Cannon算法基本思想

每一个进程只存储A、B、C矩阵的一个子块,本地相对应的A、B子块相乘后将结果累加到本地C子块上,然后再与其他进程交换A、B子块,继续相乘将结果累加到本地C子块上。但是要保证对应的A、B子块相乘,不能错位,我们注意到,在最开始,Pij上的A为所在行的第j个,B为所在列的第i个,A和B子块并不对应,因此将一行上的A子块循环向左移动i格,一列上的B子块循环向上移动j格,这样A和B子块就能对应了,以后每执行一次计算,每行所有A子块循环向左移动1格,每列上的B子块循环向上移动1格,A、B子块相乘的结果累加在本地的C子块上。

4、Cannon算法原理

将矩阵A和B分成p个方块Aij和Bij(0<=i,j<=sqrt(p)-1),每块大小为(n/sqrt(p))*(n/sqrt(p)),并将他们分配给sqrt(p)*sqrt(p)个处理器(P00,P01,...,P(sqrt(p)-1,sqrt(p)-1))。开始时处理器Pij存放有块Aij和Bij,并负责计算Cij,然后算法开始执行:
① 将块Aij(0<=i,j<=sqrt(p)-1)向左循环移动i步;
     将块Bij(0<=i,j<=sqrt(p)-1)向上循环移动j步;
② Pij执行乘 - 加运算;
    然后,将块Aij(0<=i,j<=sqrt(p)-1)向左循环移动1步;
    将块Bij(0<=i,j<=sqrt(p)-1)向上循环移动1步;
③ 重复第②步,在Pij中共执行sqrt(p)次块Aij和Bij的循环单位移步。
5、算法举例
下图示例了在16个处理器上,用Cannon算法执行A(4x4)*B(4x4)的过程。其中(a)和(b)对应于上述算法的第①步;(c)、(d)、(e)和(f)对应于上述算法的第②和第③步。注意在算法第①步时,A矩阵的第0行不移位,第1行循环左移1位,第2行循环左移2位。第3行循环左移3位;类似的,B矩阵的第0列不移位,第1列循环上移1位,第2列循环上移2位。第3列循环上移3位。

其实就是在计算Cij

二、对Cannon卡农算法初步探索(主从模式)

1、先实现一下各个进程的数据从主进程发送,(矩阵可以不是方阵、进程总数不是非要开平方)。

#include <stdio.h>
#include "mpi.h"
#include <stdlib.h>
#include <math.h>
#include "dgemm_1.h" int main(int argc, char **argv)
{
int M=4,N=4,K=4; // 矩阵维度
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=(int)sqrt(comm_sz); // A B行列分多少块
if(comm_sz!=a*a || a>M || a>N || a>K){
if(my_rank==0)
printf("error process:%d\n",comm_sz);
MPI_Finalize();
return 0;
} int saveM=M,saveN=N,saveK=K; // 为了A B能均分成块
if(M%a!=0){
M-=M%a;
M+=a;
}
if(N%a!=0){
N-=N%a;
N+=a;
}
if(K%a!=0){
K-=K%a;
K+=a;
}
int each_M=M/a,each_N=N/a,each_K=K/a; // 矩阵A B每块分多少行列数据 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)); // 保存数据计算结果 // 给矩阵A B,C赋值
init_Matrix(Matrix_A,Matrix_B,Matrix_C,M,N,K,saveM,saveN,saveK);
// 输出A,B,C
//Matrix_print2(Matrix_A,saveM,saveN,N);
//Matrix_print2(Matrix_B,saveN,saveK,K);
//Matrix_print2(Matrix_C,saveM,saveK,K);
printf("a=%d each_M=%d each_N=%d each_K=%d\n",a,each_M,each_N,each_K); start=MPI_Wtime();
// 主进程计算第1块
for(int i=0;i<each_M;i++){
for(int j=0;j<each_K;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/a)*each_M; // 每个块的行列起始位置(坐标/偏移量)
int beginCol=(i%a)*each_K;
for(int j=0;j<each_M;j++)
MPI_Send(Matrix_C+(beginRow+j)*K+beginCol,each_K,MPI_DOUBLE,i,j+each_M+each_N,MPI_COMM_WORLD);
// 发送A B每块数据
for(int k=0;k<a;k++){
int begin_part=k*each_N; // 移动A的列 B的行 即A列不同程度的左移,B行不同程度的上移
for(int j=0;j<each_M;j++)
MPI_Send(Matrix_A+(beginRow+j)*N+begin_part,each_N,MPI_DOUBLE,i,j,MPI_COMM_WORLD);
for(int p=0;p<each_N;p++)
MPI_Send(Matrix_B+(begin_part+p)*K+beginCol,each_K,MPI_DOUBLE,i,p+each_M,MPI_COMM_WORLD);
}
}
// 接收从进程的计算结果
for(int i=1;i<comm_sz;i++){
int beginRow=(i/a)*each_M;
int endRow=beginRow+each_M;
int beginCol=(i%a)*each_K;
for(int j=beginRow;j<endRow;j++)
MPI_Recv(result_Matrix+j*K+beginCol,each_K,MPI_DOUBLE,i,j-beginRow+2*each_M+each_N,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;
buffer_A=(double*)malloc(each_M*each_N*sizeof(double)); // A的均分行的数据
buffer_B=(double*)malloc(each_N*each_K*sizeof(double)); // B的均分列的数据
buffer_C=(double*)malloc(each_M*each_K*sizeof(double)); // C的均分行的数据 // 接收C块数据
for(int j=0;j<each_M;j++)
MPI_Recv(buffer_C+j*each_K,each_K,MPI_DOUBLE,0,j+each_M+each_N,MPI_COMM_WORLD,&status); for(int k=0;k<a;k++){ // 把每块数据求和
//接收A B块数据
for(int j=0;j<each_M;j++)
MPI_Recv(buffer_A+j*each_N,each_N,MPI_DOUBLE,0,j,MPI_COMM_WORLD,&status);
for(int p=0;p<each_N;p++)
MPI_Recv(buffer_B+p*each_K,each_K,MPI_DOUBLE,0,p+each_M,MPI_COMM_WORLD,&status); //计算乘积结果,并将结果发送给主进程
for(int i=0;i<each_M;i++){
for(int j=0;j<each_K;j++){
double temp=0;
for(int p=0;p<each_N;p++){
temp+=buffer_A[i*each_N+p]*buffer_B[p*each_K+j];
}
if(k==0)
buffer_C[i*each_K+j]=alpha*temp+beta*buffer_C[i*each_K+j];
else
buffer_C[i*each_K+j]+=alpha*temp;
}
}
//matMulti(buffer_A,buffer_B,buffer_C,each_row,N,each_col,alpha,beta);
}
// 将结果发送给主进程
for(int j=0;j<each_M;j++){
MPI_Send(buffer_C+j*each_K,each_K,MPI_DOUBLE,0,j+2*each_M+each_N,MPI_COMM_WORLD);
} free(buffer_A);
free(buffer_B);
free(buffer_C);
}
MPI_Finalize();
return 0;
}

三、对等模式的Cannon卡农算法

1、使用MPI_Isend实现

#include <stdio.h>
#include "mpi.h"
#include <stdlib.h>
#include <math.h>
#include "dgemm_1.h"
/****** MPI_Isend ******/
int main(int argc, char **argv)
{
int N=10000; // 矩阵维度
int my_rank,comm_sz,moveRow,moveCol; // 每个块移动位置
double start, stop; //计时时间
double alpha=2,beta=2; // 系数C=aA*B+bC
MPI_Status status;
MPI_Request reqs; MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
MPI_Comm_rank(MPI_COMM_WORLD,&my_rank); int block=(int)sqrt(comm_sz); // A B行列分多少块
if(comm_sz!=block*block || block>N ){
if(my_rank==0)
printf("error process:%d\n",comm_sz);
MPI_Finalize();
return 0;
} int saveN=N; // 为了A B能均分成块
if(N%block!=0){
N-=N%block;
N+=block;
}
int line=N/block; // 矩阵A B每块分多少行列数据
double *buffer_A,*buffer_B,*buffer_C,*ans;
buffer_A=(double*)malloc(line*line*sizeof(double)); // A的块数据
buffer_B=(double*)malloc(line*line*sizeof(double)); // B的块数据
buffer_C=(double*)malloc(line*line*sizeof(double)); // C的块数据
ans=(double*)malloc(line*line*sizeof(double)); // 临时存储每块的结果数据 if(my_rank==0){
double *Matrix_A,*Matrix_B,*Matrix_C;
Matrix_A=(double*)malloc(N*N*sizeof(double));
Matrix_B=(double*)malloc(N*N*sizeof(double));
Matrix_C=(double*)malloc(N*N*sizeof(double)); // 给矩阵A B,C赋值
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
if(i<saveN && j<saveN){
Matrix_A[i*N+j]=j-i;
Matrix_B[i*N+j]=i+j;
Matrix_C[i*N+j]=i*j;
}
else{
Matrix_A[i*N+j]=0;
Matrix_B[i*N+j]=0;
Matrix_C[i*N+j]=0;
}
}
}
//init_Matrix(Matrix_A,Matrix_B,Matrix_C,N,N,N,saveN,saveN,saveN);
// 输出A,B,C
//Matrix_print2(Matrix_A,saveN,saveN,N);
//Matrix_print2(Matrix_B,saveN,saveN,N);
//Matrix_print2(Matrix_C,saveN,saveN,N);
printf("block=%d line=%d\n",block,line); start=MPI_Wtime();
// 将每块数据分配给每个进程
for(int p=0;p<comm_sz;p++){
int beginRow=(p/block)*line; // 每个块的行列起始位置(坐标/偏移量)
int beginCol=(p%block)*line;
if(p==0){
for(int i=0;i<line;i++)
for(int j=0;j<line;j++){
buffer_A[i*line+j]=Matrix_A[i*N+j];
buffer_B[i*line+j]=Matrix_B[i*N+j];
buffer_C[i*line+j]=Matrix_C[i*N+j];
}
}
else{
if((p-p/block)<(p/block)*block)
moveRow=p-p/block+block;
else
moveRow=p-p/block;
if((p-(p%block)*block)<0)
moveCol=p-(p%block)*block+comm_sz;
else
moveCol=p-(p%block)*block;
for(int i=0;i<line;i++){
MPI_Send(Matrix_A+(beginRow+i)*N+beginCol,line,MPI_DOUBLE,moveRow,i,MPI_COMM_WORLD);
MPI_Send(Matrix_B+(beginRow+i)*N+beginCol,line,MPI_DOUBLE,moveCol,i+line,MPI_COMM_WORLD);
MPI_Send(Matrix_C+(beginRow+i)*N+beginCol,line,MPI_DOUBLE,p,i+2*line,MPI_COMM_WORLD);
}
}
}
free(Matrix_A);
free(Matrix_B);
free(Matrix_C);
}
else{
// 接收A B C块数据
for(int i=0;i<line;i++){
MPI_Recv(buffer_A+i*line,line,MPI_DOUBLE,0,i,MPI_COMM_WORLD,&status);
MPI_Recv(buffer_B+i*line,line,MPI_DOUBLE,0,i+line,MPI_COMM_WORLD,&status);
MPI_Recv(buffer_C+i*line,line,MPI_DOUBLE,0,i+2*line,MPI_COMM_WORLD,&status);
}
}
//计算第一次乘积结果
for(int i=0;i<line;i++){
for(int j=0;j<line;j++){
double temp=0;
for(int p=0;p<line;p++){
temp+=buffer_A[i*line+p]*buffer_B[p*line+j];
}
ans[i*line+j]=alpha*temp+beta*buffer_C[i*line+j];
}
}
// 移动块数据
if(my_rank==(my_rank/block)*block)
moveRow=my_rank-1+block;
else
moveRow=my_rank-1;
if((my_rank-block)<0)
moveCol=my_rank-block+comm_sz;
else
moveCol=my_rank-block;
MPI_Isend(buffer_A,line*line,MPI_DOUBLE,moveRow,3*line,MPI_COMM_WORLD,&reqs);
MPI_Isend(buffer_B,line*line,MPI_DOUBLE,moveCol,4*line,MPI_COMM_WORLD,&reqs);
//MPI_Wait(&reqs,&status); //阻塞发送矩阵到结束
for(int k=1;k<block;k++){ // 把每块数据求和
//接收A B块数据
if((my_rank+1)==((my_rank/block)+1)*block)
moveRow=my_rank+1-block;
else
moveRow=my_rank+1;
if((my_rank+block)>=comm_sz)
moveCol=my_rank+block-comm_sz;
else
moveCol=my_rank+block;
MPI_Recv(buffer_A,line*line,MPI_DOUBLE,moveRow,3*line,MPI_COMM_WORLD,&status);
MPI_Recv(buffer_B,line*line,MPI_DOUBLE,moveCol,4*line,MPI_COMM_WORLD,&status);
//计算乘积结果,并将结果发送给主进程
for(int i=0;i<line;i++){
for(int j=0;j<line;j++){
double temp=0;
for(int p=0;p<line;p++){
temp+=buffer_A[i*line+p]*buffer_B[p*line+j];
}
ans[i*line+j]+=alpha*temp;
}
}
//matMulti(buffer_A,buffer_B,buffer_C,each_row,N,each_col,alpha,beta);
// 移动块数据
if((my_rank-1)<(my_rank/block)*block)
moveRow=my_rank-1+block;
else
moveRow=my_rank-1;
if((my_rank-block)<0)
moveCol=my_rank-block+comm_sz;
else
moveCol=my_rank-block;
MPI_Isend(buffer_A,line*line,MPI_DOUBLE,moveRow,3*line,MPI_COMM_WORLD,&reqs);
MPI_Isend(buffer_B,line*line,MPI_DOUBLE,moveCol,4*line,MPI_COMM_WORLD,&reqs);
//MPI_Wait(&reqs,&status); //阻塞发送矩阵到结束
}
// 将结果发送给主进程
if(my_rank!=0){
for(int i=0;i<line;i++)
MPI_Isend(ans+i*line,line,MPI_DOUBLE,0,i+5*line,MPI_COMM_WORLD,&reqs);
//MPI_Wait(&reqs,&status); //阻塞发送矩阵到结束
}
if(my_rank==0){
// 接收从进程的计算结果
double *result_Matrix;
result_Matrix=(double*)malloc(N*N*sizeof(double)); // 保存数据计算结果
for(int i=0;i<line;i++){
for(int j=0;j<line;j++)
result_Matrix[i*N+j]=ans[i*line+j];
}
for(int p=1;p<comm_sz;p++){
int beginRow=(p/block)*line;
int endRow=beginRow+line;
int beginCol=(p%block)*line;
for(int i=beginRow;i<endRow;i++)
MPI_Recv(result_Matrix+i*N+beginCol,line,MPI_DOUBLE,p,i-beginRow+5*line,MPI_COMM_WORLD,&status);
} //Matrix_print2(result_Matrix,saveN,saveN,N);
stop=MPI_Wtime();
printf("rank:%d time:%lfs\n",my_rank,stop-start);
free(result_Matrix);
} free(buffer_A);
free(buffer_B);
free(buffer_C);
free(ans); MPI_Finalize();
return 0;
}

2、使用MPI_Sendrecv_replace memcpy实现

#include <stdio.h>
#include "mpi.h"
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "dgemm_1.h"
/*** MPI_Sendrecv_replace memcpy:string.h ***/
int main(int argc, char **argv)
{
int N=10000; // 矩阵维度
int my_rank,comm_sz;
double start, stop; //计时时间
double alpha=2,beta=2; // 系数C=aA*B+bC
MPI_Status status;
MPI_Request reqs; MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
MPI_Comm_rank(MPI_COMM_WORLD,&my_rank); int block=(int)sqrt(comm_sz); // A B行列分多少块
if(comm_sz!=block*block || block>N ){
if(my_rank==0)
printf("error process:%d\n",comm_sz);
MPI_Finalize();
return 0;
} int saveN=N; // 为了A B能均分成块
if(N%block!=0){
N-=N%block;
N+=block;
}
int line=N/block; // 矩阵A B每块分多少行列数据
int my_Row=my_rank/block; // 每个块当前位置(i,j)
int my_Col=my_rank%block; // 每个块当前位置(i,j) double *buffer_A,*buffer_B,*buffer_C,*ans;
buffer_A=(double*)malloc(line*line*sizeof(double)); // A的块数据
buffer_B=(double*)malloc(line*line*sizeof(double)); // B的块数据
buffer_C=(double*)malloc(line*line*sizeof(double)); // C的块数据
ans=(double*)malloc(line*line*sizeof(double)); // 临时存储每块的结果数据 if(my_rank==0){
double *Matrix_A,*Matrix_B,*Matrix_C;
Matrix_A=(double*)malloc(N*N*sizeof(double));
Matrix_B=(double*)malloc(N*N*sizeof(double));
Matrix_C=(double*)malloc(N*N*sizeof(double)); // 给矩阵A B,C赋值
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
if(i<saveN && j<saveN){
Matrix_A[i*N+j]=j-i;
Matrix_B[i*N+j]=i+j;
Matrix_C[i*N+j]=i*j;
}
else{
Matrix_A[i*N+j]=0;
Matrix_B[i*N+j]=0;
Matrix_C[i*N+j]=0;
}
}
}
//init_Matrix(Matrix_A,Matrix_B,Matrix_C,N,N,N,saveN,saveN,saveN);
// 输出A,B,C
//Matrix_print2(Matrix_A,saveN,saveN,N);
//Matrix_print2(Matrix_B,saveN,saveN,N);
//Matrix_print2(Matrix_C,saveN,saveN,N);
printf("block=%d line=%d\n",block,line); start=MPI_Wtime();
// 将每块数据分配给每个进程
for(int p=1;p<comm_sz;p++){
int beginRow=(p/block)*line; // 每个块的行列起始位置(坐标/偏移量)
int beginCol=(p%block)*line;
for(int i=0;i<line;i++){
memcpy(buffer_A+i*line,Matrix_A+(beginRow+i)*N+beginCol,line*sizeof(double)); // memory copy内存复制
memcpy(buffer_B+i*line,Matrix_B+(beginRow+i)*N+beginCol,line*sizeof(double));
memcpy(buffer_C+i*line,Matrix_C+(beginRow+i)*N+beginCol,line*sizeof(double));
//for(int j=0;j<line;j++){
//buffer_A[i*line+j]=Matrix_A[(beginRow+i)*N+beginCol+j];
//buffer_B[i*line+j]=Matrix_B[(beginRow+i)*N+beginCol+j];
//buffer_C[i*line+j]=Matrix_C[(beginRow+i)*N+beginCol+j];
//}
}
MPI_Send(buffer_A,line*line,MPI_DOUBLE,p,0,MPI_COMM_WORLD);
MPI_Send(buffer_B,line*line,MPI_DOUBLE,p,1,MPI_COMM_WORLD);
MPI_Send(buffer_C,line*line,MPI_DOUBLE,p,2,MPI_COMM_WORLD);
}
// id为0的处理器直接拷贝过去
for(int i=0;i<line;i++){
memcpy(buffer_A+i*line,Matrix_A+i*N,line*sizeof(double)); // memory copy内存复制
memcpy(buffer_B+i*line,Matrix_B+i*N,line*sizeof(double));
memcpy(buffer_C+i*line,Matrix_C+i*N,line*sizeof(double));
//for(int j=0;j<line;j++){
//buffer_A[i*line+j]=Matrix_A[i*N+j];
//buffer_B[i*line+j]=Matrix_B[i*N+j];
//buffer_C[i*line+j]=Matrix_C[i*N+j];
//}
} free(Matrix_A);
free(Matrix_B);
free(Matrix_C);
}
else{
// 接收A B C块数据
MPI_Recv(buffer_A,line*line,MPI_DOUBLE,0,0,MPI_COMM_WORLD,&status);
MPI_Recv(buffer_B,line*line,MPI_DOUBLE,0,1,MPI_COMM_WORLD,&status);
MPI_Recv(buffer_C,line*line,MPI_DOUBLE,0,2,MPI_COMM_WORLD,&status);
MPI_Recv(buffer_C,line*line,MPI_DOUBLE,0,2,MPI_COMM_WORLD,&status);
}
/*
将A中坐标为(i, j)的分块循环左移i步
将B中坐标为(i, j)的分块循环上移j步
*/
//MPI_Sendrecv(buffer_A,line*line,MPI_DOUBLE,get_index(my_Row,my_Col-my_Row,block),3,buffer_A,line*line,MPI_DOUBLE,get_index(my_Row,my_Col+my_Row,block),3,MPI_COMM_WORLD,&status);
//MPI_Sendrecv(buffer_B,line*line,MPI_DOUBLE,get_index(my_Row-my_Col,my_Col,block),4,buffer_B,line*line,MPI_DOUBLE,get_index(my_Row+my_Col,my_Col,block),4,MPI_COMM_WORLD,&status);
MPI_Sendrecv_replace(buffer_A,line*line,MPI_DOUBLE,get_index(my_Row,my_Col-my_Row,block),3,get_index(my_Row,my_Col+my_Row,block),3,MPI_COMM_WORLD,&status);
MPI_Sendrecv_replace(buffer_B,line*line,MPI_DOUBLE,get_index(my_Row-my_Col,my_Col,block),4,get_index(my_Row+my_Col,my_Col,block),4,MPI_COMM_WORLD,&status);
for(int k=0;k<block;k++){
// 把每块数据求和
for(int i=0;i<line;i++){
for(int j=0;j<line;j++){
double temp=0;
for(int p=0;p<line;p++){
temp+=buffer_A[i*line+p]*buffer_B[p*line+j];
}
if(k==0)
ans[i*line+j]=alpha*temp+beta*buffer_C[i*line+j];
else
ans[i*line+j]+=alpha*temp;
}
}
// 矩阵A每行左移一位,矩阵B每行上移一位
//MPI_Sendrecv(buffer_A,line*line,MPI_DOUBLE,get_index(my_Row,my_Col-1,block),5,buffer_A,line*line,MPI_DOUBLE,get_index(my_Row,my_Col+1,block),5,MPI_COMM_WORLD,&status);
//MPI_Sendrecv(buffer_B,line*line,MPI_DOUBLE,get_index(my_Row-1,my_Col,block),6,buffer_B,line*line,MPI_DOUBLE,get_index(my_Row+1,my_Col,block),6,MPI_COMM_WORLD,&status);
MPI_Sendrecv_replace(buffer_A,line*line,MPI_DOUBLE,get_index(my_Row,my_Col-1,block),5,get_index(my_Row,my_Col+1,block),5,MPI_COMM_WORLD,&status);
MPI_Sendrecv_replace(buffer_B,line*line,MPI_DOUBLE,get_index(my_Row-1,my_Col,block),6,get_index(my_Row+1,my_Col,block),6,MPI_COMM_WORLD,&status);
}
// 将结果发送给主进程
if(my_rank!=0){
MPI_Send(ans,line*line,MPI_DOUBLE,0,7,MPI_COMM_WORLD);
}
else{
// 接收从进程的计算结果
double *result_Matrix;
result_Matrix=(double*)malloc(N*N*sizeof(double)); // 保存数据计算结果
for(int i=0;i<line;i++){
for(int j=0;j<line;j++)
result_Matrix[i*N+j]=ans[i*line+j];
}
for(int p=1;p<comm_sz;p++){
int beginRow=(p/block)*line;
int beginCol=(p%block)*line;
MPI_Recv(ans,line*line,MPI_DOUBLE,p,7,MPI_COMM_WORLD,&status);
for(int i=0;i<line;i++){
memcpy(result_Matrix+(beginRow+i)*N+beginCol,ans+i*line,line*sizeof(double)); // memory copy内存复制
//for(int j=0;j<line;j++)
//result_Matrix[(beginRow+i)*N+beginCol+j]=ans[i*line+j];
}
} //Matrix_print2(result_Matrix,saveN,saveN,N);
stop=MPI_Wtime();
printf("rank:%d time:%lfs\n",my_rank,stop-start);
free(result_Matrix);
} free(buffer_A);
free(buffer_B);
free(buffer_C);
free(ans); MPI_Finalize();
return 0;
}

3、使用MPI_Sendrecv_replace、向量派生数据:MPI_Type_vector 、笛卡儿虚拟拓扑

#include <stdio.h>
#include "mpi.h"
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "dgemm_1.h"
/*** MPI_Sendrecv_replace 向量派生数据:MPI_Type_vector 笛卡儿虚拟拓扑 ***/
int main(int argc, char **argv)
{
int N=10000; // 矩阵维度
int my_rank,comm_sz;
double start, stop; //计时时间
double alpha=2,beta=2; // 系数C=aA*B+bC
MPI_Status status;
MPI_Request reqs; MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
MPI_Comm_rank(MPI_COMM_WORLD,&my_rank); int block=(int)sqrt(comm_sz); // A B行列分多少块
if(comm_sz!=block*block || block>N ){
if(my_rank==0)
printf("error process:%d\n",comm_sz);
MPI_Finalize();
return 0;
} int saveN=N; // 为了A B能均分成块
if(N%block!=0){
N-=N%block;
N+=block;
}
int line=N/block; // 矩阵A B每块分多少行列数据
int my_Row=my_rank/block; // 每个块当前位置(i,j)
int my_Col=my_rank%block; // 每个块当前位置(i,j) double *buffer_A,*buffer_B,*buffer_C,*ans;
buffer_A=(double*)malloc(line*line*sizeof(double)); // A的块数据
buffer_B=(double*)malloc(line*line*sizeof(double)); // B的块数据
buffer_C=(double*)malloc(line*line*sizeof(double)); // C的块数据
ans=(double*)malloc(line*line*sizeof(double)); // 临时存储每块的结果数据 if(my_rank==0){
double *Matrix_A,*Matrix_B,*Matrix_C;
Matrix_A=(double*)malloc(N*N*sizeof(double));
Matrix_B=(double*)malloc(N*N*sizeof(double));
Matrix_C=(double*)malloc(N*N*sizeof(double)); // 给矩阵A B,C赋值
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
if(i<saveN && j<saveN){
Matrix_A[i*N+j]=j-i;
Matrix_B[i*N+j]=i+j;
Matrix_C[i*N+j]=i*j;
}
else{
Matrix_A[i*N+j]=0;
Matrix_B[i*N+j]=0;
Matrix_C[i*N+j]=0;
}
}
}
//init_Matrix(Matrix_A,Matrix_B,Matrix_C,N,N,N,saveN,saveN,saveN);
// 输出A,B,C
//Matrix_print2(Matrix_A,saveN,saveN,N);
//Matrix_print2(Matrix_B,saveN,saveN,N);
//Matrix_print2(Matrix_C,saveN,saveN,N);
printf("block=%d line=%d\n",block,line); MPI_Datatype columntype; // 定义新列向量类型
MPI_Type_vector(line,line,N,MPI_DOUBLE,&columntype);// 块数 块长度 跨度
MPI_Type_commit(&columntype);
start=MPI_Wtime();
// 将每块数据分配给每个进程
for(int i=0,k=0;i<block;i++,k+=block)
for (int j=0;j<block;j++){
if(i==0&&j==0)
++j;
MPI_Send(Matrix_A+(i*line)*N+j*line,1,columntype,k+j,0,MPI_COMM_WORLD);
MPI_Send(Matrix_B+(i*line)*N+j*line,1,columntype,k+j,1,MPI_COMM_WORLD);
MPI_Send(Matrix_C+(i*line)*N+j*line,1,columntype,k+j,2,MPI_COMM_WORLD);
} // id为0的处理器直接拷贝过去
for(int i=0;i<line;i++){
memcpy(buffer_A+i*line,Matrix_A+i*N,line*sizeof(double)); // memory copy内存复制
memcpy(buffer_B+i*line,Matrix_B+i*N,line*sizeof(double));
memcpy(buffer_C+i*line,Matrix_C+i*N,line*sizeof(double));
} free(Matrix_A);
free(Matrix_B);
free(Matrix_C);
}
else{
// 接收A B C块数据
MPI_Recv(buffer_A,line*line,MPI_DOUBLE,0,0,MPI_COMM_WORLD,&status);
MPI_Recv(buffer_B,line*line,MPI_DOUBLE,0,1,MPI_COMM_WORLD,&status);
MPI_Recv(buffer_C,line*line,MPI_DOUBLE,0,2,MPI_COMM_WORLD,&status);
}
// 上下左右进程号 每一维的进程数 一维上网格的周期性 标识数是否可以重排序
int nbrs[4],dims[2]={block,block},periods[2]={1,1},reorder=0,coords[2];
MPI_Comm cartcomm;
// 定义虚拟进程拓扑 它是一个2维 dims:block*block的网格
MPI_Cart_create(MPI_COMM_WORLD,2,dims,periods,reorder,&cartcomm); // 将进程rank的顺序编号转换为笛卡儿坐标coords 2:维数
MPI_Cart_coords(cartcomm,my_rank,2,coords);
// 得到当前进程上下j 左右i 的进程标识
MPI_Cart_shift(cartcomm,0,coords[1],&nbrs[0],&nbrs[1]);
MPI_Cart_shift(cartcomm,1,coords[0],&nbrs[2],&nbrs[3]); /*
将A中坐标为(i, j)的分块循环左移i步
将B中坐标为(i, j)的分块循环上移j步
*/
MPI_Sendrecv_replace(buffer_A,line*line,MPI_DOUBLE,nbrs[2],3,nbrs[3],3,MPI_COMM_WORLD,&status);
MPI_Sendrecv_replace(buffer_B,line*line,MPI_DOUBLE,nbrs[0],4,nbrs[1],4,MPI_COMM_WORLD,&status); // 得到当前进程上下左右的进程标识
MPI_Cart_shift(cartcomm,0,1,&nbrs[0],&nbrs[1]);
MPI_Cart_shift(cartcomm,1,1,&nbrs[2],&nbrs[3]);
for(int k=0;k<block;k++){
// 把每块数据求和
for(int i=0;i<line;i++){
for(int j=0;j<line;j++){
double temp=0;
for(int p=0;p<line;p++){
temp+=buffer_A[i*line+p]*buffer_B[p*line+j];
}
if(k==0)
ans[i*line+j]=alpha*temp+beta*buffer_C[i*line+j];
else
ans[i*line+j]+=alpha*temp;
}
}
// 矩阵A每行左移一位,矩阵B每行上移一位
MPI_Sendrecv_replace(buffer_A,line*line,MPI_DOUBLE,nbrs[2],5,nbrs[3],5,MPI_COMM_WORLD,&status);
MPI_Sendrecv_replace(buffer_B,line*line,MPI_DOUBLE,nbrs[0],6,nbrs[1],6,MPI_COMM_WORLD,&status);
}
// 将结果发送给主进程
if(my_rank!=0){
MPI_Send(ans,line*line,MPI_DOUBLE,0,7,MPI_COMM_WORLD);
}
else{
// 接收从进程的计算结果
double *result_Matrix;
result_Matrix=(double*)malloc(N*N*sizeof(double)); // 保存数据计算结果
for(int i=0;i<line;i++){
for(int j=0;j<line;j++)
result_Matrix[i*N+j]=ans[i*line+j];
}
for(int p=1;p<comm_sz;p++){
int beginRow=(p/block)*line;
int beginCol=(p%block)*line;
MPI_Recv(ans,line*line,MPI_DOUBLE,p,7,MPI_COMM_WORLD,&status);
for(int i=0;i<line;i++){
memcpy(result_Matrix+(beginRow+i)*N+beginCol,ans+i*line,line*sizeof(double)); // memory copy内存复制
//for(int j=0;j<line;j++)
//result_Matrix[(beginRow+i)*N+beginCol+j]=ans[i*line+j];
}
} //Matrix_print2(result_Matrix,saveN,saveN,N);
stop=MPI_Wtime();
printf("rank:%d time:%lfs\n",my_rank,stop-start);
free(result_Matrix);
} free(buffer_A);
free(buffer_B);
free(buffer_C);
free(ans); MPI_Finalize();
return 0;
}  

四、附录

1、dgemm_1.h头文件

/***** 处理器逻辑阵列坐标到rank的转换 *****/
int get_index(int row,int col,int N){
return ((row+N)%N)*N+(col+N)%N;
}
/********** 输出函数 **********/
void Matrix_print2(double *A,int M,int saveN,int N)
{
for(int i=0;i<M;i++){
for(int j=0;j<saveN;j++)
printf("%.1lf ",A[i*N+j]);
printf("\n");
}
printf("\n");
}

  

 

MPI学习笔记(四):矩阵相乘的Cannon卡农算法的更多相关文章

  1. C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻

    前面三篇讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻.如果没有看过前面的文章,请到我的博客首页查看. 前面我们都是在一个项目中写了一个类来测试的,但实际开发中,我们往往要 ...

  2. IOS学习笔记(四)之UITextField和UITextView控件学习

    IOS学习笔记(四)之UITextField和UITextView控件学习(博客地址:http://blog.csdn.net/developer_jiangqq) Author:hmjiangqq ...

  3. java之jvm学习笔记四(安全管理器)

    java之jvm学习笔记四(安全管理器) 前面已经简述了java的安全模型的两个组成部分(类装载器,class文件校验器),接下来学习的是java安全模型的另外一个重要组成部分安全管理器. 安全管理器 ...

  4. Learning ROS for Robotics Programming Second Edition学习笔记(四) indigo devices

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

  5. Typescript 学习笔记四:回忆ES5 中的类

    中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...

  6. ES6学习笔记<四> default、rest、Multi-line Strings

    default 参数默认值 在实际开发 有时需要给一些参数默认值. 在ES6之前一般都这么处理参数默认值 function add(val_1,val_2){ val_1 = val_1 || 10; ...

  7. muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制

    目录 muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制 eventfd的使用 eventfd系统函数 使用示例 EventLoop对eventfd的封装 工作时序 runInLoo ...

  8. python3.4学习笔记(四) 3.x和2.x的区别,持续更新

    python3.4学习笔记(四) 3.x和2.x的区别 在2.x中:print html,3.x中必须改成:print(html) import urllib2ImportError: No modu ...

  9. Go语言学习笔记四: 运算符

    Go语言学习笔记四: 运算符 这章知识好无聊呀,本来想跨过去,但没准有初学者要学,还是写写吧. 运算符种类 与你预期的一样,Go的特点就是啥都有,爱用哪个用哪个,所以市面上的运算符基本都有. 算术运算 ...

  10. 零拷贝详解 Java NIO学习笔记四(零拷贝详解)

    转 https://blog.csdn.net/u013096088/article/details/79122671 Java NIO学习笔记四(零拷贝详解) 2018年01月21日 20:20:5 ...

随机推荐

  1. 深度剖析:Dubbo使用Nacos注册中心的坑

    2020年笔者在做微服务部件升级时,Dubbo的注册中心从Zookeeper切换到Nacos碰到个问题,最近刷Github又有网友提到类似的问题,就在这篇文章里做个梳理和总结. 1.问题描述 前几年我 ...

  2. 如何在openGauss 2.1.0中使用Job

    如何在 openGauss 2.1.0 中使用 Job 如何在 openGauss 2.1.0 中使用 Job Job 类似 unix 中的 crontab,有定时执行的功能,可以在指定的时间点或每天 ...

  3. openGauss/MogDB配置IPv6

    openGauss/MogDB 配置 IPv6 openGauss/MogDB 支持多种网络接口,假如我们想在支持 IPv6 的网络上部署使用,只需简单操作即可,本文将介绍在 Centos 上如何配置 ...

  4. k8s之构建Mysql和Wordpress集群

    一.实验目的 基于Kubernetes集群实现多负载的WordPress应用.将WordPress数据存储在后端Mysql,Mysql实现主从复制读写分离功能. 工作负载 服务 持久卷 Mysql S ...

  5. locust常用的配置参数【locust版本:V1.1.1】

    locust 官网文档地址:https://docs.locust.io/en/stable/configuration.html Locust QQ 群: 执行命令如: master: locust ...

  6. tomcat 服务版本内存设置

    1. 安装服务,如需指定java路径,需要在service.bat 中修改, 如下图 其中 pa代表当前目录 2. 安装服务, service.bat install 服务名,如下图示例 3. 内存设 ...

  7. Dapr Outbox 执行流程

    Dapr Outbox 是1.12中的功能. 本文只介绍Dapr Outbox 执行流程,Dapr Outbox基本用法请阅读官方文档 .本文中appID=order-processor,topic= ...

  8. 力扣574(MySQL)-当选者(中等)

    题目: 表: Candidate 表: Vote id 是自动递增的主键,CandidateId 是 Candidate 表中的 id. 问题:请编写 sql 语句来找到当选者的名字,上面的例子将返回 ...

  9. CF1913C Game with Multiset 题解

    [题目描述] 你有一个空的多重集,你需要处理若干下列询问: ADD $ x $:加入一个数值为 $ 2^x $ 的元素到该多重集. GET $ w $:判断是否存在一个该多重集的子集,使得这个子集的所 ...

  10. HarmonyOS NEXT应用开发之预加载so并读取RawFile文件

    介绍 本示例主要介绍在TaskPool子线程中使用 dlopen 预加载 so 库并使用句柄调用库函数的方法,以及在Native中使用 pread 系统函数读取Rawfile文件的部分文本内容,并添加 ...