基于C语言实现并行程序设计

本文详细探讨了使用OpenMP和MPI实现树型求和、蝶式求和以及MyBcast操作。实验涵盖了并行计算的不同策略,包括在MPI中实现的树型和蝶型求和,以及一个自定义的广播操作。此外,还对比了MPI版本的Alltoall和Allgather操作与标准实现的性能。最后,文章讨论了LU分解和QR分解的并行化实现,分析了线程数对计算时间的影响,并给出了性能对比。
摘要由CSDN通过智能技术生成

资源下载地址:https://download.csdn.net/download/sheziqiong/86771681
资源下载地址:https://download.csdn.net/download/sheziqiong/86771681

lab1

  1. 分别用omp和mpi实现树型求和和蝶式求和
    树型omp实现思路:对每一层的计算并行化,对叶节点编号,序号值为线程号。
    考虑兄弟结点相加时把结果存在序号较小的结点中。
    将二叉树每一次计算步骤编号为1-n步,因为任务数为2的幂次方,因 此n= log_2⁡N
    要并行化的计算是每一步的节点与兄弟节点的加法。每一步的节点序 号与兄弟节点序号的差值为2^(n−i),i为步数。将加法交予不同的线程并行实现。
    分发结果就是求和的逆向实现,区别在于是自上而下的。

    实验结果:

    树型mpi实现思路:在计算全和时,在第i步,进程id整除2^(n−i +1) 的负责接收数据并求和,其余进程负责发送数据。
    在分发全和时,在第i步,进程id整除2^(n−i+1) 的负责发送数据,其余进程负责接收数据。
    发送与接收数据的进程编号在第i步相差2^(n−i) 。

    //主要代码:  
    for (int i = 1; i <= logN; i++)
    {
        int tag = i;
        int step = (int)pow(2, i);
        if (pid % step == 0)
        {
            MPI_Recv(&recvdata, 1, MPI_INT, pid+step/2,     tag, MPI_COMM_WORLD, &status);
            data += recvdata;
        }
        else
         if(pid % step == step/2)
        {
            MPI_Send(&data, 1, MPI_INT, pid-step/2, tag,    MPI_COMM_WORLD);
        }
    }
    //spread
    for (int i = logN; i >0; i--)
    {
        int tag = i;
        int step = (int)pow(2, i);
        if (pid % step == 0)
        {
            MPI_Send(&data, 1, MPI_INT, pid+step/2, tag,    MPI_COMM_WORLD);
        }
        else
        if(pid % step == step/2)
        {
            MPI_Recv(&recvdata, 1, MPI_INT, pid-step/2,     tag, MPI_COMM_WORLD, &status);
            data = recvdata;
        }
    
    }    
    

    实验结果:

    蝶式求和omp实现思路:仍然是对每一步的计算并行化,对叶节点编 号,序号值为线程号。考虑兄弟结点相加时把结果存在序号较小的结点中。
    依然将计算步骤编号从开始到结束编为1-n步,因为任务数为2的幂次方,因此n= log_2⁡N
    要并行化的计算是每一步的节点与兄弟节点的加法。关键在于如何确定相加节点的序号差值。
    将编号写为n位的二进制格式,可以发现,在第i步,相加的节点序号恰好在第i位不同,而其余位都相同。

    //主要代码:
    for (int i = 0; i < logN; i++)
    {
        step = (int)pow(2, i);
    #pragma omp parallel private(pid, tmp, dest)
        {
            pid = omp_get_thread_num();
            tmp = num[pid];
            dest = pid^step;
            tmp = num[pid] + num[dest];
    #pragma omp barrier
            num[pid] = tmp;
    #pragma omp barrier
        }
    }
    

    实验结果:

    蝶式求和mpi实现思路:思路与omp实现相同,区别在于不用关心哪两个节点相加,
    此时发送目的进程和接受来源进程的id的二进制格式在第i步时的第i位不同。

    //主要代码:  
    int logN = (int)log2(num_procs);
    for(int i = 0; i < logN; i++) {
        int tag = i+1;
        int step = (int)pow(2,i);
        int dest = id_procs ^ step;
        MPI_Send(&data, 1, MPI_INT, dest, tag,  MPI_COMM_WORLD);
        MPI_Recv(&recvdata, 1, MPI_INT, dest, tag,  MPI_COMM_WORLD, &status);
        data += recvdata;
    }
    

    实验结果:

  2. 实现MyBcast()
    思路:
    (1)将MPI进程按所在节点划分子通讯域N;
    (2)可以将各子通讯域的首进程(编号为0)再组成一个子通讯域H;
    (3)由广播的root进程将消息发给原来最大通讯域中的0号进程h,再由h在H通讯域中广播(MPI_Bcast),各首进程然后在各自子通讯域N中再行广播(MPI_Bcast);
    (4)子通讯域H:将N的首进程通过MPI_Group_incl()函数建立一个组,再用MPI_Comm_create()建立子通讯域H。

    //伪代码
    MPI_Comm_split(MPI_COMM_WORLD, color, key, &split_comm_world);
    建立MPI_COMM_WORLD的进程组World_Group;
    通过World_Group建立h_Group;
    PI_Comm_create(MPI_COMM_WORLD, h_Group, &h_comm_world);
    oot进程发送消息:
    	MPI_Send(data, count, MPI_TYPE, 0, 1, MPI_COMM_WORLD);
    原通讯域的0号进程接收:
        MPI_Recv(data, count, MPI_TYPE, root, 1, MPI_COMM_WORLD,&status);
    号进程在H中广播:
    	 MPI_Bcast(data, count, MPI_TYPE, 0, h_comm_world);
    在N中广播
    	 MPI_Bcast(data, count, MPI_TYPE, 0, split_comm_world);
    ```http://www.biyezuopin.vip
    ```c
    //主要代码:
    MPI_Comm h_comm_world;
    MPI_Comm_group(MPI_COMM_WORLD, &world_group);
    int grpsize = num_procs / 2;
    int zerolist[] = {0, 1, 2, 3};
    int zerocnt = 0;
    
    MPI_Group_incl(world_group, grpsize, zerolist, &    new_group);
    MPI_Comm_create(MPI_COMM_WORLD, new_group, &    h_comm_world);
    // message from root to 0 proc of MPI_COMM_WORLD
    if (id_procs == root)
    {
        MPI_Send(&seq, 16, MPI_CHAR, 0, 1, MPI_COMM_WORLD);
    }
    else if (id_procs == 0)
    {
        MPI_Recv(&seq, 16, MPI_CHAR, root, 1,   MPI_COMM_WORLD, &status);
    }
    MPI_Barrier(MPI_COMM_WORLD);
    // Broadcast within the group H
    if(h_comm_world != MPI_COMM_NULL)
        MPI_Bcast(&seq, 16, MPI_CHAR, 0, h_comm_world);
    MPI_Barrier(MPI_COMM_WORLD);
    //Broadcasr within the group N
    
    MPI_Bcast(&seq, 16, MPI_CHAR, 0, split_comm_world);
    MPI_Barrier(MPI_COMM_WORLD);
    

    实验结果:

  3. 用MPI_Send和MPI_Recv来模拟实现诸如MPI_Alltoall, MPI_Allgather功能并与标准MPI实现做简要性能对比.

    //MPI_Alltoall实现
    void MPI_Alltoall_my(int* senddata, int sendcount, MPI_Datatype senddatatype, int* recvdata, int recvcount,
    MPI_Datatype recvdatatype, MPI_Comm comm)
    {
        int rank, size;
        MPI_Status status;
        MPI_Comm_rank(comm, &rank);
        MPI_Comm_size(comm, &size);
        for (int i = 0; i < size; i++)
        {
            if (i != rank)
            {
                MPI_Send(senddata + i * sendcount, sendcount,       senddatatype, i, rank , comm);
                MPI_Recv(recvdata + i * recvcount, recvcount,       recvdatatype, i, i, comm, &status);
            }
            else
            {
            	//memcpy(recvdata + i * recvcount, senddata,        sizeof(senddatatype)*sendcount);
            	recvdata[i] = senddata[i];
            }
        }
    }
    
    //MPI_Allgather实现  
    void MPI_Allgather_my(int* senddata, int sendcount, MPI_Datatype senddatatype, int* recvdata, int recvcount,
    MPI_Datatype recvdatatype, MPI_Comm comm)
    {
        int rank, size, i;
        MPI_Status status;
        MPI_Comm_rank(comm, &rank);
        MPI_Comm_size(comm, &size);
    
        for (i = 0; i < size; i++)
        {
            if (i != rank)
            {
                MPI_Send(senddata, sendcount, senddatatype, i,      rank , comm);
                MPI_Recv(recvdata + i * recvcount, recvcount,       recvdatatype, i, i, comm, &status);
            }
            else
            {
            	//memcpy(recvdata + i * recvcount, senddata,        sizeof(senddatatype)*sendcount);
            	recvdata[i] = *senddata;
            	}
        }
    }
    

    性能对比:

lab2

  1. 用mpi和omp实现LU分解

    • 原理
      利用主行i对j >i的其他行做初等行变换,由于各行计算时没有数据相关,因此可以对矩阵按行划分做并行计算。课本的并行算法考虑到各处理器之间的负载均衡,使用的是交叉行划分。划分后各处理器轮流选出主行并广播给其他处理器,其他处理器利用接收的主行对部分行向量进行计算。
    • 思路
      在课本第18章附录2的LU分解的MPI实现上,对处理器用主行做行变换的循环部分用openmp实现for编译制导,将计算平均分给各线程处理,其他不动。

    实验结果(只跑了6维矩阵和5维矩阵作为演示):


    性能对比:

    线程数100 * 1001000 * 100010000 * 10000
    20.1143s0.55024s237.638s
    40.0968s0.508s229.686s
    80.1019s0.349117s201.801s
  2. 用纯omp实现QR分解
    QR分解的串行算法有三层循环,直接对最内层循环做并行化处理。

    //主要代码  
    for(int j = 0; j < N; j++){
        for (int i = j+1; i < N; i++)
        {
            sq = sqrt(A(j, j) * A(j, j) + A(i, j) * A(i, j));
            c = A(j, j)/sq;
            s = A(i, j)/sq;
            int k ;
        #pragma omp parallel for private(k)
            for (k = 0; k < N; k++)
            {
                Aj[k] = c * A(j, k) + s * A(i, k);
                Qj[k] = c * Q(j, k) + s * Q(i, k); 
                Ai[k] = - s * A(j, k) + c * A(i, k);
                Qi[k] = - s * Q(j, k) + c * Q(i, k);
            }
        #pragma omp parallel for private(k)
            for (k = 0; k < N; k++)
            {
                A(j, k) = Aj[k];
                A(i, k) = Ai[k];
                Q(i, k) = Qi[k];
                Q(j, k) = Qj[k];
            }         
        }
    }
    

    性能对比:

    线程数矩阵维数n=8矩阵维数n=16矩阵维数n=32
    10.0000730.0002930.001481
    20.0001822.1367280.002181
    40.4348244.5819920.004917
    81.97092110.21833045.5784
    分析:
    我们惊讶的发现,即使不考虑节点自身的问题————如运算资源在被其他程序所消耗等,线程数增加后,运行时间反而增大了。特别是在线程数增加到8后,对32维矩阵的QR分解时间增大到一种吓人的地步。我从知乎上得到了一个很有说服力的解释:
    嵌套循环,内层循环并行。并行线程的创建与销毁会有开销,在嵌套循环的时候如果对内层for并行的话,这个开销会很大。解决方法是把并行放在外层的for。
  3. 用mpi实现summa算法
    原理:

    性能对比:

    length/process number1248
    160.0000230.0000510.0001481.002412
    2560.1297140.0439310.0231010.011794
    10249.2663331.9760201.2651440.594526

资源下载地址:https://download.csdn.net/download/sheziqiong/86771681
资源下载地址:https://download.csdn.net/download/sheziqiong/86771681

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值