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

按照老师所说,可以把矩阵的每一行都存其列号(0~N-1),然后列向量全部设置为1,这样得到的结果列向量一定每一位的值都应当是(N-1+0)*N/2,可以用这种方式检查程序写的对不对。

每个进程一次读完自己的任务

简述

假设机器是共享内存的(如果是分布式内存的,那16台这么大内存的机器能处理的规模是现在的16倍),那么必须为所有进程考虑这点可用的空间,拿N=40000试了一下(死循环扔进后台然后查看内存使用情况):
这里写图片描述
因为最主要的就是存这个大方阵,它所开的空间是N*N*sizeof(double)就等于40000*40000*8个字节,差不多是12.8个GB,如上图。

按照这种计算方式,用阶数是50000的方阵时所需要的空间差不多是20个G,一定不够用。

不过这种死循环式的测试手段似乎有问题,没有考虑操作系统本身。操作系统依据程序局部性原理,把不断命中的while(1){}这部分语句已经保持在了内存甚至cache里,然而这部分语句完全没有用到我要测试的那些malloc出来的内存里的数据,所以没有使用的那部分数据或许被暂时换到外存,所以这种测试得到的结果是这样:
这里写图片描述

实际上在这个程序里,不是真的能跑这么大的,如果放到前台来执行,跑不出结果来:
这里写图片描述
只好Ctrl+Z停止然后kill掉,在kill之前可以看一下主存和交换分区都满了,我猜测是因为不停的缺页替换所以始终没法得到结果。

把自己能处理的jobs都kill掉,然后看一下到底有多少内存可用:
这里写图片描述
差不多19980000MB=1.998x10^10B,4万多阶的double方阵就是极限了。

程序
#include<stdio.h>
#include<mpi.h>
#include<stdlib.h>

//方阵的维度
#define N 40000

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号进程存总的结果

    /**********************/
    MPI_Init(NULL,NULL);
    MPI_Comm_rank(MPI_COMM_WORLD,&my_rank);
    MPI_Comm_size(MPI_COMM_WORLD,&comm_sz);
    //本进程处理的行数就是总阶数/进程数
    my_row=N/comm_sz;

    //每个进程都申请空间
    vec=malloc(N*sizeof(int));
    mat=malloc(N*my_row*sizeof(double));
    result=malloc(my_row*sizeof(double));

    //结果向量的分量初始值置0
    for(i=0;i<my_row;i++)
        result[i]=0;

    //矩阵的值的设定
    for(i=0;i<my_row;i++)
        for(j=0;j<N;j++)
            mat[i*N+j]=j;
    //向量的值的设定
    for(j=0;j<N;j++)
        vec[j]=1;

    //进程同步,让所有进程的资源都准备好
    MPI_Barrier(MPI_COMM_WORLD);

    //复杂度O(N*my_row)=O(N*N/comm_sz)
    for(i=0;i<my_row;i++)
    {
        for(j=0;j<N;j++)
        {
            //计算并加进来
            result[i]+=mat[i*N+j]*vec[j];
        }
    }

    //while(0){}

    //进程同步,让所有进程的资源都准备好
    MPI_Barrier(MPI_COMM_WORLD);

    //聚集给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);

    //0号进程负责输出
    if(my_rank==0)
    {
        printf("The Result is:\n");
        for(i=0;i<N;i+=1)
            printf("%f\n",all_rst[i]);
    }

    MPI_Finalize();
    /**********************/

    free(vec);
    free(mat);
    free(result);
    free(all_rst);

    return 0;
}
运行结果

这里写图片描述

这里写图片描述
方阵每行是从0到4万-1,用计算器计算一下4万*(4万-1)/2,结果正确。

每个进程按行读取自己的任务

简述

受群里ll同学的方法启发,他的方式是把每个进程自己的小矩阵,再分配成多行一组的行组,每次计算出这些行组对应的子向量后,把行组free掉重新申请,这样只需要行组这么多的空间,就可以轮流读入和计算更多行的子矩阵。

我让这种方式处在极限状态下,即每行本身是前面所说的一个行组。
申请好这一行的空间后,进程每次读入一行覆盖上一次读入的这行,即反复使用这一行的空间,计算并放入自己负责的子向量中去。
因为每次只是覆盖上一次的值,一行始终是这么多数,不需要重复free和*alloc申请空间。
这里写图片描述

如果每个进程本身在单独的一台机器上,这种方式理论上能计算的方阵的阶的极限是:用进程所在的机器几乎全部的内存表示到一行,所能表示的向量阶数。如果每台机器都是16G的内存,前面计算了能开的大矩阵也就是4万多阶,就按4万阶算,那么理论上能计算约40000*40000=16亿行。

程序

ll同学在群里跑到了96万,我也按96万试一下(实际上这两种方式在这台机器上能跑的阶都远不止96万,但是跑的时间实在太长了)。

在测试中,为了快速获得结果和正当结束程序,可以把对一行值的设定放到循环外面(反正测试的是都一样的值),并把输出时跳跃的步长从1改成几万,这样很快就会采样输出完然后结束。

/*
15121856 刘知昊
第1次作业(方阵*向量,按行分配)
 */
#include<stdio.h>
#include<mpi.h>
#include<stdlib.h>

//方阵的维度
#define N 960000

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);
    //本进程处理的行数就是总阶数/进程数
    my_row=N/comm_sz;

    //为每个进程都申请空间
    vec=malloc(N*sizeof(int));
    //mat=malloc(N*my_row*sizeof(double));//这是个进程每次申请完
    row_now=malloc(N*sizeof(double));//这是每个进程每次仅申请一行
    result=calloc(sizeof(double),my_row);//为了初始值置0而使用calloc

    //向量的值的设定,使用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;

        for(j=0;j<N;j++)
        {
            //计算并加入到本进程结果向量的对应位置上
            result[i]+=row_now[j]*vec[j];
        }

        //下次循环直接覆盖上次使用的值
        //因此不需free和重新申请row_now
    }

    //为了机器考虑,确定不再使用的空间立即free掉
    free(row_now);

    //while(0){}//测试用

    //此处的进程同步是必要之举
    MPI_Barrier(MPI_COMM_WORLD);

    //聚集给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);

    //为了机器考虑,确定不再使用的空间立即free掉
    free(vec);
    free(result);
    //free(mat);


    //0号进程负责输出
    if(my_rank==0)
    {
        printf("The Result is:\n");
        //改变跨度可以采样获取结果,快速结束I/O
        for(i=0;i<N;i+=1)
            printf("%f\n",all_rst[i]);
    }

    MPI_Finalize();
    /**********************/

    //最终,free应无遗漏
    free(all_rst);

    return 0;
}
运行结果

这里写图片描述

这里写图片描述
方阵每行是从0到96万-1,用计算器计算一下96万*(96万-1)/2,结果正确。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值