【MPI学习笔记】4:并行化方阵和向量的乘积(按列分配)

记录一下传文件到服务器上的命令,不然每次都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次)。

在数据量很大的时候差异明显。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值