并行算法实验设计报告

时间:2024.4.13

两个矩阵相乘的行列划分并行算法

摘要:给定两个阶矩阵,矩阵乘法是指计算 ,现在对两个矩阵乘法进行串行和并行的实验和分析

关键词:并行算法;矩阵相乘;

算法原理:

1、串行算法

通常的矩阵乘矩阵的串行计算过程如算法1所示,此外为计算矩阵相乘,还可以有对3层循环采用其他嵌套形式的串行算法。

算法1: 稠密矩阵相乘的形式串行算法

2、并行算法

两个矩阵相乘的行列划分并行算法假设一共有个进程,将矩阵按行分成个块,将矩阵按列分成个块:

每块包含连续若干个行.为使得负载平衡,应使得每块中的行数尽量相等.将分别存储在进程中.将分为块,且将存储在中,如算法2

算法2 稠密矩阵乘的行列划分并行算法

实验由MPICH2在VS2010上进行并行环境的配置来完成,单机情况下用进程数的个数模拟多处理器。

在实验中算法由以下几个函数实现:

void readData();此函数被rankID为0的进程调用,负责从dataIn.txt文件中A[M,K],B[P,N]两个相乘矩阵的数据,并为结果矩阵C[M,N]分配空间。其中C[N,N]=A[M,K]*B[P,N]。

int gcd(int M,int N,int group_size) 此函数用来返回两个整数的不大于group_size的最大公因子,即算法所用到的处理器个数,为了保证行划分和列划分可以平均的划分,通过求M,N不大于group_size的最大公因子来确定实际用到的处理器p。

void printResult();此函数被rankID为0的进程调用,用来将A,B,C矩阵打印输出给用户,并输出用于分发数据和并行计算的时间。

int main(int argc, char **argv) ;程序的主函数。

算法分析(可扩展性分析):

在LogP模型上,算法2并行执行时间为:

由此可知,并行效率为:

因此,等效率函数为:

算法的MPI程序:

// matrix.cpp : 定义控制台应用程序的入口点。

//

#include "stdafx.h"

#include "stdio.h"

#include "stdlib.h"

#include "mpi.h"

#include<mpicxx.h>

#define intsize sizeof(int)

#define floatsize sizeof(float)

#define charsize sizeof(char)

#define A(x,y) A[x*K+y]

#define B(x,y) B[x*N+y]

#define C(x,y) C[x*N+y]

#define a(x,y) a[x*K+y]

#define b(x,y) b[x*n+y]

#define buffer(x,y) buffer[x*n+y] /* 此宏用来简化对标号为奇数的处理器内的缓冲空间的访问 */

#define c(l,x,y) c[x*N+y+l*n]

float *a,*b,*c,*buffer;

int s;

float *A,*B,*C;            /* A[M,K],B[P,N].正确的情况下K应该等于P,否则无法进行矩阵相乘 */

int M,N,K,P ;

int m,n;

int myid;

int p;                     /* 保存工作站集群中处理器数目,也即通信子大小 */

FILE *dataFile;            /* 用于读取输入文件内容和将计算结果输出到结果文件的临时文件指针 */

MPI_Status status;

double time1;

double starttime,endtime;

/*

 * 函数名: readData

 * 功能:  此函数被rankID为0的进程调用,负责从dataIn.txt文件中读入

 *         A[M,K],B[P,N]两个相乘矩阵的数据,并为结果矩阵C[M,N]分配空间。

 *         其中C[N,N]=A[M,K]*B[P,N]

 * 输入:  无

 * 返回值:无

 */

void readData()

{

    int i,j;

    starttime = MPI_Wtime();

    dataFile=fopen("dataIn.txt","r");

    fscanf(dataFile,"%d%d", &M, &K);              /* 读取矩阵A的行,列数M,K */

    A=(float *)malloc(floatsize*M*K);             /* 为矩阵A分配空间 */

    for(i = 0; i < M; i++)                        /* 读入矩阵A的各元素 */

    {

        for(j = 0; j < K; j++)

        {

            fscanf(dataFile,"%f", A+i*K+j);

        }

    }

    fscanf(dataFile,"%d%d", &P, &N);              /* 读取矩阵B的行,列数P,N */

    if (K!=P)                                     /* K应该等于P,否则矩阵无法相乘 */

    {

        printf("the input is wrong\n");

        exit(1);

    }

    B=(float *)malloc(floatsize*K*N);             /* 为矩阵B分配空间 */

    for(i = 0; i < K; i++)                        /* 从文件中读入矩阵B的各元素 */

    {

        for(j = 0; j < N; j++)

        {

            fscanf(dataFile,"%f", B+i*N+j);

        }

    }

    fclose(dataFile);

    printf("Input of file \"dataIn.txt\"\n");

    printf("%d\t %d\n",M, K);                     /* 输出A矩阵的维数 */

    for(i=0;i<M;i++)                              /* 输出A矩阵的数据 */

    {

        for(j=0;j<K;j++) printf("%f\t",A(i,j));

        printf("\n");

    }

    printf("%d\t %d\n",K, N);                     /* 输出B矩阵的维数 */

    for(i=0;i<K;i++)                              /* 输出B矩阵的数据 */

    {

        for(j=0;j<N;j++) printf("%f\t",B(i,j));

        printf("\n");

    }

    C=(float *)malloc(floatsize*M*N);             /* 为结果矩阵C[M,N]分配空间 */

}

/*

 * 函数名: gcd

 * 功能:  此函数用来返回两个整数的不大于group_size的最大公因子

 * 输入:  M,N:要求最大公因数的两个整数

 *         group_size所求公因子必须小于此参数,此参数代表用户指定的通信子大小

 * 返回值:M和N的不大于group_size的最大公因子

 */

int gcd(int M,int N,int group_size)

{

    int i;

    for(i=M; i>0; i--)

    {

        if((M%i==0)&&(N%i==0)&&(i<=group_size))

            return i;

    }

    return 1;

}

/*

 * 函数名: printResult

 * 功能:此函数被rankID为0的进程调用,用来将A,B,C矩阵打印输出给用户,

 *       并输出用于分发数据和并行计算的时间

 * 输入:无

 * 返回值:无

 */

void printResult()

{

    int i,j;

    printf("\nOutput of Matrix C = AB\n");

    for(i=0;i<M;i++)                              /* 输出C矩阵的结果数据 */

    {

        for(j=0;j<N;j++) printf("%f\t",C(i,j));

        printf("\n");

    }

    endtime=MPI_Wtime();

    printf("\n");

    printf("Whole running time    = %f seconds\n",endtime-starttime);

    printf("Distribute data time  = %f seconds\n",time1-starttime);

    printf("Parallel compute time = %f seconds\n",endtime-time1);

}

/*

 * 函数名: main

 * 功能:程序的主函数

 * 输入:argc为命令行参数个数;

 *       argv为每个命令行参数组成的字符串数组。

 * 输出:返回0代表程序正常结束;其它值表明程序出错。

 */

int main(int argc, char **argv)

{

    int i,j,k,l,group_size,mp1,mm1;

    MPI_Init(&argc,&argv);

    MPI_Comm_size(MPI_COMM_WORLD,&group_size);

    MPI_Comm_rank(MPI_COMM_WORLD,&myid);

    p=group_size;

//下面一段程序负责从dataIn.txt文件中读入A[M,K],B[P,N]两个相乘矩阵的数据,

//并为结果矩阵C[M,N]分配空间。C[N,N]=A[M,K]*B[P,N]

//注意这段程序只有编号为0的处理器才执行此步操作

    if(myid==0)

    {

        readData();

    }

    if (myid==0)                                  /* 由编号为0的进程将A,B两矩阵的行列维数M,K,N发送给所有其他进程 */

        for(i=1;i<p;i++)

    {

        MPI_Send(&M,1,MPI_INT,i,i,MPI_COMM_WORLD);

        MPI_Send(&K,1,MPI_INT,i,i,MPI_COMM_WORLD);

        MPI_Send(&N,1,MPI_INT,i,i,MPI_COMM_WORLD);

    }

    else                                          /* 编号非0的进程负责接收A,B两矩阵的行列维数M,K,N */

    {

        MPI_Recv(&M,1,MPI_INT,0,myid,MPI_COMM_WORLD,&status);

        MPI_Recv(&K,1,MPI_INT,0,myid,MPI_COMM_WORLD,&status);

        MPI_Recv(&N,1,MPI_INT,0,myid,MPI_COMM_WORLD,&status);

    }

    p=gcd(M,N,group_size);

    m=M/p;                                        /* m代表将矩阵按行分块后每块的行数 */

    n=N/p;                                        /* m代表将矩阵按列分块后每块的列数 */

    if(myid<p)

    {

        a=(float *)malloc(floatsize*m*K);         /* a[m,K]用来存储本处理器拥有的矩阵A的行块 */

        b=(float *)malloc(floatsize*K*n);         /* b[K,n]用来存储此时处理器拥有的矩阵B的列块 */

        c=(float *)malloc(floatsize*m*N);         /* c[m,N]用来存储本处理器计算p-1次得到所有结果 */

        if (myid%2!=0)                            /* 为标号为奇数的处理器分配发送缓冲空间 */

            buffer=(float *)malloc(K*n*floatsize);

        if (a==NULL||b==NULL||c==NULL)            /* 如果分配空间出错,则打印出错信息 */

            printf("Allocate space for a,b or c fail!");

        if (myid==0)                              /* 标号为0的处理器将应该它拥有的矩阵A,B的元素读入自己的a,b中 */

        {

            for (i=0;i<m;i++)

                for (j=0;j<K;j++)

                    a(i,j)=A(i,j);

            for (i=0;i<K;i++)

                for (j=0;j<n;j++)

                    b(i,j)=B(i,j);

        }

        if (myid==0)                              /* 标号为0的处理器将其他处理器的初始数据分别发给各处理器 */

        {

            for (i=1;i<p;i++)

            {

                MPI_Send(&A(m*i,0),K*m,MPI_FLOAT,i,i,MPI_COMM_WORLD);

                for (j=0;j<K;j++)

                    MPI_Send(&B(j,n*i),n,MPI_FLOAT,i,i,MPI_COMM_WORLD);

            }

            free(A);

            free(B);                              /* 至此,A,B两矩阵的数据已经完全被分散到各处理器。释放A,B所占空间 */

        }

        else                                      /* 标号非0的处理器从0处理器接受各自的初始矩阵数据 */

        {

            MPI_Recv(a,K*m,MPI_FLOAT,0,myid,MPI_COMM_WORLD,&status);

            for (j=0;j<K;j++)

                MPI_Recv(&b(j,0),n,MPI_FLOAT,0,myid,MPI_COMM_WORLD,&status);

        }

        if (myid==0)

            time1=MPI_Wtime();                    /* 标号为0的处理器记录开始矩阵相乘计算的时间 */

        for (i=0;i<p;i++)                         /* 一共进行p轮计算 */

        {

            l=(i+myid)%p;

            for (k=0;k<m;k++)

                for (j=0;j<n;j++)

                    for (c(l,k,j)=0,s=0;s<K;s++)

                        c(l,k,j)+=a(k,s)*b(s,j);

            mm1=(p+myid-1)%p;                     /* 计算本进程的前一个进程的标号 */

            mp1=(myid+1)%p;                       /* 计算本进程的后一个进程的标号 */

            if (i!=p-1)

            {

                if(myid%2==0)                     /* 偶数号处理器先发送后接收 */

                {

                    MPI_Send(b,K*n,MPI_FLOAT,mm1,mm1,MPI_COMM_WORLD);

                    MPI_Recv(b,K*n,MPI_FLOAT,mp1,myid,MPI_COMM_WORLD,&status);

                }

                else                              /*奇数号处理器先将B的列块存于缓冲区buffer中,然后接收编号在其后面的

                                                    处理器所发送的B的列块,最后再将缓冲区中原矩阵B的列块发送给编号

                                                    在其前面的处理器 */

                {

                    for(k=0;k<K;k++)

                        for(j=0;j<n;j++)

                            buffer(k,j)=b(k,j);

                    MPI_Recv(b,K*n,MPI_FLOAT,mp1,myid,MPI_COMM_WORLD,&status);

                    MPI_Send(buffer,K*n,MPI_FLOAT,mm1,mm1,MPI_COMM_WORLD);

                }

            }

        }

        if (myid==0)                              /* 标号为0的进程直接将计算结果保存到结果矩阵C中 */

            for(i=0;i<m;i++)

                for(j=0;j<N;j++)

                    C(i,j)=*(c+i*N+j);

        if (myid!=0)                              /* 标号非0的进程则要把计算结果发送到标号为0的处理器中去 */

            MPI_Send(c,m*N,MPI_FLOAT,0,myid,MPI_COMM_WORLD);

        else                                      /* 标号为0的进程负责接收其他进程的计算结果并保存到结果矩阵C中 */

        {

            for(k=1;k<p;k++)

            {

                MPI_Recv(c,m*N,MPI_FLOAT,k,k,MPI_COMM_WORLD,&status);

                for(i=0;i<m;i++)

                    for(j=0;j<N;j++)

                        C((k*m+i),j)=*(c+i*N+j);

            }

        }

        if(myid==0)       /* 0号处理器负责将A,B,C矩阵打印输出给用户,并输出用于分发数据和并行计算的时间 */

            printResult();

    }

    MPI_Finalize();

    if(myid<p)            /* 释放所有临时分配空间 */

    {

        free(a);

        free(b);

        free(c);

        if(myid==0)       /* 只有0号进程要释放C */

            free(C);

        if(myid%2!=0)     /* 只有奇数号进程要释放buffer */

            free(buffer);

    }

       printf("%d",p);

       getchar();

    return (0);

}

算例,结果及分析:

上面的dataIn.txt是两个10乘以10的矩阵,总的时间是0.197588秒,可以看到主要的时间用来通讯和分发数据,并行计算的时间其实较短。

换一个算例,即10乘以20矩阵乘以20乘以10 的矩阵,下面是运行时间:

数据量增大,但是并行时间并没有大量增加,而是在通讯时间上面消耗更多时间,跟算法可扩展的等效率分析一致。

参考文献

·      [1] 都志辉,《高性能计算之并行编程技术—— MPI并行程序设计》

·      [2] 《数值并行算法与软件》,科学出版社, 20##-08-01

                    


          

更多相关推荐:
算法设计实验报告

算法设计课程报告课题名称算法设计与实现课题负责人名学号张樱紫0743111317同组成员名单角色无指导教师左劼评阅成绩评阅意见提交报告时间20xx年12月23日课程名称算法设计学生姓名张樱紫学生学号074311...

算法设计实验报告一

计算机算法设计与分析实验报告

算法设计实验报告模板

算法设计与分析实验报告实验三贪心算法实验目的1理解贪心算法的概念2掌握贪心算法的基本要素3理解贪心算法与动态规划算法的差异4理解贪心算法的一般理论实验项目1活动安排问题2最优装载问题3多机调度实验步骤请附上编写...

算法设计与分析实验报告

算法设计与分析实专业班级学生姓名号验报告学一实验目的与要求1熟悉CC语言的集成开发环境2通过本实验加深对递归过程的理解二实验内容掌握递归算法的概念和基本思想分析并掌握整数划分问题的递归算法三实验题任意输入一个整...

算法设计实验报告一_

算法设计实验报告一实验内容题目1编程实现常见排序算法如插入排序归并排序快速排序随机化的快速排序等并统计不同输入规模下1组从小到大排序1组从大到小排序其余8组随机的平均物理执行时间2编程实现循环赛日程表设有n2k...

算法设计实验报告

算法设计基础实验班级学号姓名实验一线性表的应用一实验要求给定一线性表L15250536788523写出顺序存储和链接存储结构下的插入删除排序操作的算法及程序二实验代码includeltiostreamhgtin...

算法设计与分析实验报告

算法分析与设计实验报告实验报告题目实验一递归与分治策略一实验目的1加深学生对分治法算法设计方法的基本思想基本步骤基本方法的理解与掌握2提高学生利用课堂所学知识解决实际问题的能力3提高学生综合应用所学知识解决实际...

算法设计实验报告

算法设计与分析实验报告姓名学号班级指导教师实验一1分别用递归法和动态规划法求Fibonacci数给出实2现代码并填写下表表2不同方法所用时间ms实现代码递归算法includequotstdiohquotincl...

计算机算法与设计分析实验报告

计算机算法与设计分析实验报告班级姓名学号目录实验一分治与递归11基本递归算法12棋盘覆盖问题23二分搜索34实验小结5实验二动态规划算法51最长公共子序列问题52最大子段和问题73实验小结8实验三贪心算法81多...

-算法设计与分析实验报告格式 2

算法设计与分析实验报告一实验名称统计数字问题评分实验日期年月日指导教师刘长松姓名兜兜专业班级计算机10学号20xx一实验要求1掌握算法的计算复杂性概念2掌握算法渐近复杂性的数学表述3掌握用C语言描述算法的方法4...

算法设计与分析实验报告

算法设计与分析实验报告指导老师沙莎学院信息科学与工程学院班级计科0508姓名戚婕学号10完成日期20xx年12月目录实验一分治法211实验要求212实验内容213核心算法214程序代码415实验结果8实验二21...

算法设计与分析的实验报告

实验一递归与分治策略一实验目的1加深学生对分治法算法设计方法的基本思想基本步骤基本方法的理解与掌握2提高学生利用课堂所学知识解决实际问题的能力3提高学生综合应用所学知识解决实际问题的能力二实验内容1设a0n1是...

算法设计实验报告(36篇)