MPI学习笔记(四):矩阵相乘的Cannon卡农算法
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位。
二、对Cannon卡农算法初步探索(主从模式)
1、先实现一下各个进程的数据从主进程发送,(矩阵可以不是方阵、进程总数不是非要开平方)。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | #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实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | #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实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | #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 、笛卡儿虚拟拓扑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | #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头文件
1 2 3 4 | /***** 处理器逻辑阵列坐标到rank的转换 *****/ int get_index( int row, int col, int N){ return ((row+N)%N)*N+(col+N)%N; } |
1 2 3 4 5 6 7 8 9 10 | /********** 输出函数 **********/ 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" ); } |
本文作者:惊小呆
本文链接:https://www.cnblogs.com/babyclass/p/16633535.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
分类:
标签:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步