记录一下传文件到服务器上的命令,不然每次都history找很烦:
scp -P 5006 /home/lzh/文档/mpi/rate2.c student@hpc.shu.edu.cn:/home/student/15121856/rate2.c
简述
这个和上一篇一样,也是多机上追求速度。按列分配时,我的做法还是每个进程获得自己要处理的那块数据,而省去Scatter的时间。
按列分配,也是可以直接从文件里拿出自己要的数据的,因为文件指针的移动还是非常有规律的。每次读自己处理的那么多(my_col)列,然后就加N-my_col移动到下一行要开始读的位置。
剩下的做法和按行分一样了,这样的若干(my_col)列其实就是N短行,每短行若干(my_col)个元素。
代码
/*
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_col;//本进程处理的列数
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_col=N/comm_sz;
//对于列向量,每个进程只申请自己这列需要的空间
vec=malloc(my_col*sizeof(int));
//mat=malloc(N*my_col*sizeof(double));//这是每个进程每次申请完
row_now=malloc(my_col*sizeof(double));//这是每个进程每次仅申请一短行
result=malloc(N*sizeof(double));//结果数组,最后所有进程的要加起来
//自己的向量的值的设定
//实际使用时从文件读入自己的那部分
for(j=0;j<my_col;j++)
vec[j]=1;
/*
//本短行的值的设定
//在实际使用时,这里是读入本进程应处理的下一短行
//只需要文件指针大跳N-my_col步,然后一步一步跳读入my_col个数
for(j=0;j<my_col;j++)
row_now[j]=my_rank*my_col+j;
*/
//复杂度O(N*my_col)=O(N*N/comm_sz)
for(i=0;i<N;i++)//一共还是N短行
{
//还是每短行0,1,2..这样
for(j=0;j<my_col;j++)
row_now[j]=my_rank*my_col+j;
result[i]=row_now[0]*vec[0];
//对于列位置上的值
for(j=1;j<my_col;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);
//free(vec);
//new:为了效率考虑,这里先不free
//while(0){}//测试用
//old:此处的进程同步是必要之举
//MPI_Barrier(MPI_COMM_WORLD);
//new:并不必要,没能运行到这行的进程无法参与Reduce!
//且参与过Reduce的进程不会影响暂时未参与Reduce的进程
//归约给0号进程
if(my_rank==0)
{
//先开辟存储空间
all_rst=malloc(N*sizeof(double));
//将0号进程自己的result写入
for(i=0;i<N;i++)
all_rst[i]=result[i];
/*
一种改进的想法是,用0号进程自己的result数组,
作为MPI_Reduce归约的第2个参数.
书上指出这种方式可能会出现混乱,不建议我们这样做
*/
//归约
MPI_Reduce
(
result, /*发送内容的地址*/
all_rst, /*接收内容的地址*/
N, /*归约操作的长度*/
MPI_DOUBLE, /*归约的数据类型*/
MPI_SUM, /*归约操作符*/
0, /*接收至哪个进程*/
MPI_COMM_WORLD /*通信域*/
);
}
else
{
//归约
MPI_Reduce
(
result, /*发送内容的地址*/
all_rst, /*接收内容的地址*/
N, /*归约操作的长度*/
MPI_DOUBLE, /*归约的数据类型*/
MPI_SUM, /*归约操作符*/
0, /*接收至哪个进程*/
MPI_COMM_WORLD /*通信域*/
);
}
//old:进程同步,让所有进程的资源都准备好
//MPI_Barrier(MPI_COMM_WORLD);
//new:不必要
//old:为了机器考虑,确定不再使用的空间立即free掉
//free(result);
//new:为了效率考虑,这里先不free
//0号进程负责输出
if(my_rank==0)
{
//计时结束
end=time(NULL);
//计算时间
printf("time=%f\n",difftime(end,start));
printf("The Result is:\n");
//改变跨度可以采样获取结果,快速结束I/O
//new:尽量别整除,哈希式的采样才是有意义的
for(i=0;i<N;i+=N/11)
printf("%f\n",all_rst[i]);
}
MPI_Finalize();
/**********************/
//最终,free应无遗漏
free(all_rst);
//new:
free(row_now);
free(vec);
free(result);
return 0;
}
测试
这么短的时间,而且是在我把循环体内的重复赋值打开的情况下得到的(在按行分时我没有打开,而是使用了循环体外的一次赋值)。
想了半天为什么按列分这么快,我只能理解成MPI_Reduce归约要比MPI_Gather聚集快,或者是-O3做了不少优化(对每个进程而言,按列分的行数>按行分的行数,如果全在cache中,只考虑第一次访内存的时间,按行分要访N个,而按列分只要访my_rol次)。
在数据量很大的时候差异明显。