【MPI学习笔记】3:快速并行方阵和向量乘积+多机测试
2018-01-22 23:39
309 查看
简述
之前使用的是在一台机器上的,内存非常有限,而核心数也不是很多,为了减小机器承受的压力,每运行到某块*alloc出的内存必定不被使用时,就立即free掉,而在多机上,这样的压力分散到了多台机器上。按照这次作业的要求,需要让计算速度尽可能快,这样就应当能不free的尽量不free,能不同步的不做同步,从而快速得到结果,再去free申请来的内存。程序中还有一些细节,如当j=0时,操作row_now[j]必定会比操作row_now[0]慢,因为对j的取值解析也需要一次访问内存,为了至高的速度考虑,程序中应尽量避免这样的浪费。
上次课时,何老师指出,不必要的calloc也是一种浪费,因为对内存的清空,即初始化全0,也是要耗费时间的。在个问题中这样做是冗余的,因此只需要在j=0时对其做赋值,而其它时候做+=操作。
另外,如果在for循环中只对游标等于某个数值时做if判断,是非常不值得的,因为if的条件判断也是要耗费时间的,所以这部分被我重构了。
代码
注释中,用old:标注的是之前的做法和考虑,new:标注是新的做法和考虑。/* 15121856 刘知昊 第2次作业(方阵x向量按行分配,计时) */ #include<stdio.h> #include<mpi.h> #include<stdlib.h> #include<time.h> //方阵的维度 #define N 960000 time_t start,end;//开始和结束时间 int main() { int *vec=NULL;//列向量 //double *mat=NULL;//自己进程的那部分矩阵 int my_rank;//自己进程的进程号 int comm_sz;//总的进程数目 int my_row;//本进程处理的行数 int i,j;//通用游标 double *result=NULL;//用来存本进程计算出的结果向量 double *all_rst=NULL;//只给0号进程存总的结果 double *row_now=NULL;//每个进程每次仅申请的一行 /**********************/ MPI_Init(NULL,NULL); MPI_Comm_rank(MPI_COMM_WORLD,&my_rank); MPI_Comm_size(MPI_COMM_WORLD,&comm_sz); //计时开始 if(my_rank==0) { start=time(NULL); } //本进程处理的行数就是总阶数/进程数 my_row=N/comm_sz; //为每个进程都申请空间 vec=malloc(N*sizeof(int)); //mat=malloc(N*my_row*sizeof(double));//这是个进程每次申请完 row_now=malloc(N*sizeof(double));//这是每个进程每次仅申请一行 result=malloc(my_row*sizeof(double));//old:为了初始值置0而使用calloc //new:为了效率而用malloc,只要在i=0时做赋值 //向量的值的设定,使用memset会报错 for(j=0;j<N;j++) vec[j]=1; //本行的值的设定 //在实际使用时,这里是读入本进程应处理的下一行 for(j=0;j<N;j++) row_now[j]=j; //复杂度O(N*my_row)=O(N*N/comm_sz) for(i=0;i<my_row;i++) { /* //本行的值的设定 //在实际使用时,这里是读入本进程应处理的下一行 for(j=0;j<N;j++) row_now[j]=j; */ //j=0时做赋值,后面就都加上来,这样就不需malloc result[i]=row_now[0]*vec[0]; for(j=1;j<N;j++) { /*old: //计算并加入到本进程结果向量的对应位置上 if(j==0) result[i]=row_now[0]*vec[0];//用0比用j少一次向内存寻址! */ //上面这种方式的缺陷在于,每次都要多执行一下if判断 //即使是执行简单的if判断,在数量级很大时也是很耗时间的 //所以拿到函数体外 //计算并加入到本进程结果向量的对应位置上 result[i]+=row_now[j]*vec[j]; } //下次循环直接覆盖上次使用的值 //因此不需free和重新申请row_now } //old:为了机器考虑,确定不再使用的空间立即free掉 //free(row_now); //new:为了效率考虑,这里先不free //while(0){}//测试用 //old:此处的进程同步是必要之举 //MPI_Barrier(MPI_COMM_WORLD); //new:并不必要,Gather本身会去管理进程传来信息的位置 //聚集给0号进程 if(my_rank==0) { //先开辟存储空间 all_rst=malloc(N*sizeof(double)); //聚集 MPI_Gather ( result, /*发送内容的地址*/ my_row, /*发送的长度*/ MPI_DOUBLE, /*发送的数据类型*/ all_rst, /*接收内容的地址*/ my_row, /*接收的长度*/ MPI_DOUBLE, /*接收的数据类型*/ 0, /*接收至哪个进程*/ MPI_COMM_WORLD /*通信域*/ ); } else { //聚集 MPI_Gather ( result, /*发送内容的地址*/ my_row, /*发送的长度*/ MPI_DOUBLE, /*发送的数据类型*/ all_rst, /*接收内容的地址*/ my_row, /*接收的长度*/ MPI_DOUBLE, /*接收的数据类型*/ 0, /*接收至哪个进程*/ MPI_COMM_WORLD /*通信域*/ ); } //进程同步,让所有进程的资源都准备好 //MPI_Barrier(MPI_COMM_WORLD); //new:不必要 //old:为了机器考虑,确定不再使用的空间立即free掉 //free(vec); //free(result); //free(mat); //new:为了效率考虑,这里先不free //0号进程负责输出 if(my_rank==0) { //计时结束 end =time(NULL); //计算时间 printf("time=%f\n",difftime(end,start)); printf("The Result is:\n"); //改变跨度可以采样获取结果,快速结束I/O for(i=0;i<N;i+=N/10) printf("%f\n",all_rst[i]); } MPI_Finalize(); /**********************/ //最终,free应无遗漏 free(all_rst); //new: free(row_now); free(vec); free(result); return 0; }
测试
因为有其他同学也在用,他们在做什么事会直接影响我测试出的时间,在4主机48进程下测试了三次,时间的单位是秒:我试过,如果直接把上一次(没有做速度上的优化时)的代码-O3编译,然后4主机48进程下跑(也就是说单纯依赖核心数的增加,而不对代码做速度上的优化)时,这个时间是380秒左右,相比之下,依赖重构在速度上优化了一半左右的时间。
当然,对速度的追求,造成了内存的浪费。如果真的有OPT算法,那么内存的浪费也完全没有了(可惜只能想想)。
相关文章推荐
- 【MPI学习笔记】4:并行化方阵和向量的乘积(按列分配)
- 【MPI学习笔记】5:传统形式的方阵向量并行乘法及性能测试
- 【MPI学习笔记】2:并行化方阵和向量的乘积(按行分配)
- 【MPI学习笔记】1:并行化向量和矩阵的乘积
- 【OpenMP学习笔记】4:传统形式的方阵向量并行乘法
- MPI学习笔记之并行程序概述
- MPI学习笔记之并行程序概述
- MPI学习笔记之并行程序概述
- MPI学习笔记之并行程序概述
- MPI学习笔记之并行程序概述
- MPI学习笔记之并行程序概述
- MPI学习笔记之并行程序概述
- MPI学习笔记之并行程序概述
- MPI学习笔记之并行程序概述
- MPI学习笔记之并行程序概述
- 并行计算学习笔记(MPI)
- MPI学习笔记之并行程序概述
- MPI学习笔记之并行程序概述
- Python 学习笔记1 - 快速入门与web2py
- Linux学习笔记2:如何快速的学习使用一个命令