欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-03-15 19:25:55
...

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

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

简述

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

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

不过这种死循环式的测试手段似乎有问题,没有考虑操作系统本身。操作系统依据程序局部性原理,把不断命中的while(1){}这部分语句已经保持在了内存甚至cache里,然而这部分语句完全没有用到我要测试的那些malloc出来的内存里的数据,所以没有使用的那部分数据或许被暂时换到外存,所以这种测试得到的结果是这样:
【MPI学习笔记】2:并行化方阵和向量的乘积(按行分配)

实际上在这个程序里,不是真的能跑这么大的,如果放到前台来执行,跑不出结果来:
【MPI学习笔记】2:并行化方阵和向量的乘积(按行分配)
只好Ctrl+Z停止然后kill掉,在kill之前可以看一下主存和交换分区都满了,我猜测是因为不停的缺页替换所以始终没法得到结果。

把自己能处理的jobs都kill掉,然后看一下到底有多少内存可用:
【MPI学习笔记】2:并行化方阵和向量的乘积(按行分配)
差不多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;
}

运行结果

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

【MPI学习笔记】2:并行化方阵和向量的乘积(按行分配)
方阵每行是从0到4万-1,用计算器计算一下4万*(4万-1)/2,结果正确。

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

简述

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

我让这种方式处在极限状态下,即每行本身是前面所说的一个行组。
申请好这一行的空间后,进程每次读入一行覆盖上一次读入的这行,即反复使用这一行的空间,计算并放入自己负责的子向量中去。
因为每次只是覆盖上一次的值,一行始终是这么多数,不需要重复free和*alloc申请空间。
【MPI学习笔记】2:并行化方阵和向量的乘积(按行分配)

如果每个进程本身在单独的一台机器上,这种方式理论上能计算的方阵的阶的极限是:用进程所在的机器几乎全部的内存表示到一行,所能表示的向量阶数。如果每台机器都是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;
}

运行结果

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

【MPI学习笔记】2:并行化方阵和向量的乘积(按行分配)
方阵每行是从0到96万-1,用计算器计算一下96万*(96万-1)/2,结果正确。

相关标签: 并行计算 MPI