lab4 PCG Solver¶
实验步骤¶
访存优化¶
基准代码中,不连续的内存访问存在于对矩阵 A 对角线元素的访问中,通过将 A 对角线元素预先取出存放到连续的内存中,提高访存效率。
线程级并行(OpenMP)¶
使用 OpenMP 将循环并行化。
对于循环体之间无依赖的迭代,在循环前添加如下指令使其并行化:
#pragma omp parallel for schedule(static)
对于进行累加操作的迭代,使用归约 (reduction) 使其并行化(对 sum 进行累加):
#pragma omp parallel for reduction(+:sum) schedule(static)
并且使用 static 调度方式,使各个块包含的迭代次数平均。
进程级并行(MPI)¶
使用 MPI 开启不同进程进行并行计算。
整体思路为:根进程在计算开始前将矩阵 A 和数组 b 广播给各个进程,将 MatrixMulVec 函数中的矩阵与向量相乘运算分发给各个进程进行计算,计算完毕之后将各个进程结果进行合并并且广播,其余部分各个进程独立计算。
采用上述方式,尽量避免通信带来过大的开销,并且尽量加速时间消耗最大的矩阵与向量相乘运算。
源代码及 Makefile 文件位于当前目录下 pcg_c 文件夹内。
Note
其中 Makefile 文件进行了两处更改:
| Makefile | |
|---|---|
1 2 | |
使用 Intel 提供的编译器 mpiicc 。
根据官方文档,添加 -qopenmp 编译选项启用 OpenMP 。
使用 4 节点 8 任务对三组输入进行测试,使用 sbatch 提交如下脚本:
#!/bin/bash
#SBATCH -o out.txt
#SBATCH -N 4
#SBATCH -n 8
#SBATCH --exclusive
#SBATCH --cpus-per-task 12
source /opt/intel/oneapi/setvars.sh
ulimit -s unlimited
mpirun ./pcg input_2.bin | grep -v 'mpool' | grep -v 'ib_md'
结果分别用时 3.97 s 、17.48 s、91.90 s 。
Profile¶
以输入 input_2.bin 为例,使用 Intel Trace Analyzer and Collector 进行性能分析。

由图可得,耗时最多的三个 MPI 函数分别为 MPI_Allgather 、 MPI_Bcast 、 MPI_Comm_rank 。

可得程序消耗的总时间为 103 s ,其中消耗在 MPI 上的总时间为 37.9 s 。
打开事件时间轴 (Event Timeline) 可以看到各个进程的运行情况:

由图可得,程序一共开启了 8 个进程,调用 MPI 的开销主要的位于每次进入 PCG 函数时调用 MPI_Bcast 函数。
截取一小段时间轴进行放大,可以得知在一次 PCG 函数的调用中,MPI_Allgather 函数被调用了多次:

此外,右键下方的 Group MPI 选择 Ungroup MPI ,可以看到各个 MPI 函数的调用次数及时间消耗:

Bonus¶
使用 Fortran 完成实验。
源代码及 Makefile 文件位于当前目录下 pcg_fortran 文件夹内。
Note
Makefile 文件如下:
| Makefile | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
使用的 Fortran 编译器为 Intel 提供的 mpiifort 。
将 pcg.f90 、 main.f90 、 judge.c 分别编译汇编为 .o 文件后,再进行链接形成可执行文件 pcg 。
以使用 4 节点 8 任务对三组输入进行测试,使用 sbatch 提交如下脚本:
#!/bin/bash
#SBATCH -o out.txt
#SBATCH -N 4
#SBATCH -n 8
#SBATCH --exclusive
#SBATCH --cpus-per-task 12
source /opt/intel/oneapi/setvars.sh
ulimit -s unlimited
mpirun ./pcg input_2.bin | grep -v 'mpool' | grep -v 'ib_md'
以 input_2.bin 为例,Fortran 用时 45.75 s ,C 用时 17.48 s 。(还没弄清楚为什么相差这么多)
使用 C 实现参与排名计算分数。