天津大学并行计算实验四:多级并行化计算矩阵幂

往期实验的传送门:
实验二:多线程计算矩阵幂
实验一:多线程计算正弦值
实验三:多进程计算矩阵幂

前言

其实在实验三能顺利做完,有了代码的框架之后,再做实验四其实没什么难度了,只需要稍微改点东西。不过这里我怀疑我的代码也存在一些问题,因为最终分析加速比的时候,实验测出来的实验四的效果不如实验三。所以这里只能给大家仅供参考了,也欢迎大家指出我的问题到底在哪里。

免责声明:注意:代码等仅供同学们加深对课程知识点的理解,严禁抄袭,要是查重(不确定有没有)什么的被老师抓到了后果自负

环境要求

由于本次实验是MPI+OpenMP,所以和上次实验一样,需要先加载一下mpi环境,就跟着pdf指导书里面输入指令。

module load openmpi/4.1.4-mpi-x-gcc9.3.0

就是让虚拟机配置一下mpi的环境,然后才有办法编译用mpi库的程序。

算法介绍

这次实验的算法主体上是在实验三的代码基础上稍加改动,可以先去看看文章最前面的实验三传送门。实验三中是创建多个进程,将矩阵划分为多个子矩阵再分别进行矩阵乘法运算。本次实验的多级并行化,就是让每个进程分到的子矩阵,再次用多线程,进一步划分成更小的子矩阵之后让每个线程算最小的子矩阵。然后再汇总,其他大体跟实验三相同。

然后具体应该如何使用OpenMP多线程呢,可以参考一下ppt课件的实例:
实例
然后我们可以模仿着ppt里面的例子写,OpenMP的多线程利用tid号进行操作,具体的看下面代码里我的操作。

算法流程图:
在这里插入图片描述

先上代码,然后讲解在代码注释里面:

#include <iostream>
#include <mpi.h>
#include <time.h>
#include <omp.h>
#define ll long long
using namespace std;

const int mod = 1e9 + 7;

int main(int argc, char** argv) {
    srand(time(0));
    int my_rank;
    int num_procs;
    int size = atoi(argv[1]);
    int cnt = atoi(argv[2]);

    MPI_Init(NULL, NULL);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &num_procs);

    int line = size / num_procs; // 每个进程计算的行数
    int* local_a = new int[line * size];
    int* b = new int[size * size];
    int* ans = new int[line * size];
    int* a = new int[size * size];
    int* c = new int[size * size]; //用来缓冲区接受数据
    ll trace = 0;

    //cout << "mpi ran = " << my_rank << endl;

    if (my_rank == 0) {

        for (int i = 0; i < size; i++) { // a是计算矩阵,b是结果矩阵
            for (int j = 0; j < size; j++) {
                a[i * size + j] = rand() % 10;
                // a[i * size + j] = 1;
                b[i * size + j] = 0;
                if (i == j)
                    b[i * size + j] = 1;
            }
        }

        for (int ii = 1; ii <= cnt; ii++) {
            MPI_Scatter(a, line * size, MPI_INT, local_a, line * size, MPI_INT, 0,
                        MPI_COMM_WORLD);

            MPI_Bcast(b, size * size, MPI_INT, 0, MPI_COMM_WORLD);
            int tid, nthreads;
            omp_set_num_threads(4);
            #pragma omp parallel private(tid)
            {
                nthreads = omp_get_num_threads();
                tid = omp_get_thread_num(); // 当前线程的编号

                // cout << "openmp tid = " << tid << endl;
                // cout << "nthreads = " << nthreads << endl;
                for (int i = tid; i < line; i += nthreads){ // 每个线程计算该进程中分配到的若干行
                    for (int j = 0; j < size; j++) {
                        int temp = 0;
                        for (int k = 0; k < size; k++)
                            temp = (temp + 1ll * a[i * size + k] * b[k * size + j]) % mod;
                        ans[i * size + j] = temp;
                    }
                    // printf("nthreads ii rank tid = %d %d %d %d \n", nthreads, ii, my_rank, tid);
                }
            }

            MPI_Gather(ans, line * size, MPI_INT, c, line * size, MPI_INT, 0,
                    MPI_COMM_WORLD);
            for (int i = 0; i < size; i++)
                for (int j = 0; j < size; j++)
                    b[i * size + j] = c[i * size + j];
        }
        // cout << "ans:" << endl;
        // for (int i = 0; i < size; i++) {
        //     for (int j = 0; j < size; j++) {
        //         cout << b[i * size + j] << " ";
        //     }
        //     cout << endl;
        // }
        // for (int i = 0; i < size; i++)
        //     for (int j = 0; j < size; j++)
        //         trace = (trace + b[i * size + j]) % mod;
        // cout << trace << endl;
    } else {
        while (cnt--) {
            int* buffer = new int[size * line];
            MPI_Scatter(a, line * size, MPI_INT, buffer, line * size, MPI_INT, 0,
                        MPI_COMM_WORLD);
            MPI_Bcast(b, size * size, MPI_INT, 0, MPI_COMM_WORLD);

            // 接受矩阵后,利用openmp多线程计算buffer跟b的矩阵乘法
            // 即将line再次划分,给多线程计算
            omp_set_num_threads(4);
            int tid, nthreads;
            #pragma omp parallel private(tid)
            {
                // cout << "fuck" << endl;
                nthreads = omp_get_num_threads(); // 总线程数
                tid = omp_get_thread_num(); // 当前线程的编号
                // cout << nthreads << endl;
                for (int i = tid; i < line; i += nthreads){
                    for (int j = 0; j < size; j++) {
                        int temp = 0;
                        for (int k = 0; k < size; k++)
                            temp = (temp + 1ll * buffer[i * size + k] * b[k * size + j]) % mod;
                        ans[i * size + j] = temp;
                    }
                }
            //printf("rank tid = %d %d \n", my_rank, tid);
            }

            MPI_Gather(ans, line * size, MPI_INT, c + my_rank * line * size,
                    line * size, MPI_INT, 0, MPI_COMM_WORLD);
            delete[] buffer;
        }
    }

    delete[] a, local_a, b, ans, c;

    MPI_Finalize();
    return 0;
}

代码中有很大一部分是注释掉的,这部分内容主要是为了保证矩阵乘法的正确性,可以通过小规模的矩阵的输出矩阵,来验证一下我们的矩阵乘法程序的正确性。

写好程序后,注意现在的编译命令改为

mpic++ -fopenmp -o bing.o bing.cpp

如果前面没有输那句 module load配一下mpi环境的话编译会报错。

因为这次懒得写串行了,我就直接用单核单线程来替代串行程序,脚本如下:

#!/bin/bash
module load openmpi/4.1.4-mpi-x-gcc9.3.0

# time yhrun -p thcp1 -N 1 -n 1 -c 1 bing.o 10 2 &> chuan.log

# time yhrun -p thcp1 -N 2 -n 2 -c 4 bing.o 960 200 &> bing.log
# time yhrun -p thcp1 -N 2 -n 2 -c 4 bing.o 1600 10 &> bing.log

time yhrun -p thcp1 -N 2 -n 2 -c 4 bing.o 80 500 &> bing.log
time yhrun -p thcp1 -N 2 -n 2 -c 4 bing.o 240 100 &> bing.log
time yhrun -p thcp1 -N 2 -n 2 -c 4 bing.o 240 200 &> bing.log
time yhrun -p thcp1 -N 2 -n 2 -c 4 bing.o 496 100 &> bing.log
time yhrun -p thcp1 -N 2 -n 2 -c 4 bing.o 496 200 &> bing.log
time yhrun -p thcp1 -N 2 -n 2 -c 4 bing.o 960 200 &> bing.log
time yhrun -p thcp1 -N 2 -n 2 -c 4 bing.o 1600 10 &> bing.log


编写好脚本bing.sh后,最后提交任务

yhbatch -p thcp1 -N 2 -n 2 ./bing.sh

注意事项

这里有两个地方,是我当初卡了一段时间的问题,我上面的代码是已经解决了我发现的问题的最终版本。

问题一:矩阵划分问题

因为是模仿实验三,实验三中特地强调了,我们输入的矩阵规模必须是8的倍数。然后在这次实验中一开始我忘了这个事情,开始测的就是 10 × 10 10 \times 10 10×10 的规模,然后也没错。
然后我想了很久,最后发现,因为实验三是MPI那块,分8个进程,所以规模是8的倍数。然后这次实验里,最后指令只要求MPI分2个进程,所以只要是2的倍数都可以正常计算,而里面细分的多线程是没有均分的要求限制,故其实就是正确的。

问题二:加速比异常问题

噔噔咚,每次实验必遇到的加速比异常来咯
一开始我算出来加速比超级高,远超8倍。为了检测问题,果断在sh脚本中改成单核单线程运行,发现显然比穿行时间要快。所以控制了一下变量,发现只可能是OpenMP的多线程有问题。

然后输出一下tid,发现虽然我们脚本里控制 -c4,只使用4个线程,但是实际上他使用的远超4个线程,所以实际上的并行核心数量远超8个,所以加速比爆炸了。

因为后面还需要分析不同实验的加速比,所以还是得控制一下变量,就让2个进程,每个进程内的OpenMP都使用4个线程了。所以可以看到我上面的代码中有一行

omp_set_num_threads(4);

就是为了让他控制一下只使用4个线程。

尾声

到此,四次实验都完成了,最后还会交一个大作业,就是对比后三次实验,不同并行计算方式情况下的加速比等,还需要搞个ppt和录视频介绍。

为了要对比,所以最后还是把实验二重新测了一次,不然这几次实验的数据规模不同,不太好分析。然后会发现,我实验二的方式,是9个线程,所以最后我还是按照行划分的方式,重新写了一次实验二,重新测数据了。

至于加速比等的对比,我这里建议将数据放到excel上,然后用excel来画一些柱形图之类的,看起来能比较美观一些。当然如果会python画图啥的肯定更好。

也欢迎大家写一写自己的实验记录,功在当代,利在千秋啊,这就是TJU的精神传承。

  • 24
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
天津大学并行计算实验主要是针对计算机科学与技术专业的学生开展的一项实践性课程。通过这门实验课程,学生们能够学习并了解并行计算的原理、方法和相关技术。 在实验中,学生们会针对并行计算的相关概念和理论进行学习,包括并行计算的定义、并行计算的应用领域以及并行计算的发展历程等等。随后,学生们将根据所学知识的积累,进行实际的并行计算实验。 在这些实验中,学生们将使用计算机集群或者多核计算机等并行计算平台,通过编写并行计算程序来解决一些复杂的计算问题。例如,他们可以利用并行计算方法来加快大规模科学计算、图像处理、数据挖掘等任务的执行速度。 实验中,学生们将会学习到并行计算的设计与优技术,如任务分割与调度、数据划分与通信、负载平衡等。同时,他们还将学习到编程模型和并行计算框架的使用,例如MPI、OpenMP、CUDA等。 通过进行并行计算实验,学生们能够进一步了解并掌握并行计算的核心概念与技术,提高问题解决能力,并为今后从事相关领域的研究与开发奠定坚实的基础。同时,实验还能培养学生的团队协作意识和解决实际问题的能力。 作为天津大学计算机科学与技术专业的一门核心实验课程,这门并行计算实验在培养学生的实践能力、提高学生的创新精神和科研素质方面发挥了重要作用。同时,这门实验课程也为学生今后的科学研究和工程实践提供了宝贵的经验和资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值