赛题克隆与优化
一、赛题克隆
1. 克隆赛题项目
我将其克隆到家目录下,为了减少输入修改了目录名字(不过后面又记为变量了,感觉没什么太大用处)。然后在环境变量里新增该目录为变量$QUEST1
git clone /home/tangzehao
mv OpenCAEPoro_ASC2024 OpenCAEPoro
vim ~/.bashrc
# 键入i编辑,追加以下指令
export QUEST1=/home/tangzehao/OpenCAEPoro
# 按Esc退出编辑,然后输入:wq保存退出
2. 解压缩源文件
进入项目文件夹,接着解压所有的源文件。
cd /home/tangzehao/OpenCAEPoro
tar zxf lapck*
tar zxf parmetis*
tar zxf hypre*
tar zxf petsc-3.19.3*
tar zxf petsc_solver*
tar zxf OpenCAEPoro*
二、编译与测试
进入对应包的文件夹,按以下顺序编译
1. 编译lapck
make blaslib
make cblaslib
make lapacklib
make lapackelib
2. 编译parmetis
按照官方文档编译这个包的时候一直报错,找不到mpiicc,于是我把参数改成cc=gcc, cxx=g++,又在#include <mpi.h>报错,搜索发现是没有安装mpich¹,由于本机未安装且没有管理员权限,需要手动安装到家目录,于是我又手动安装mpich。在跑通baseline后才知道这里可以用icc,但是已经编译完了,我就懒得再改回去了(笑)。
这里手动安装mpich²
# 下载和解压
wget https://www.mpich.org/static/downloads/4.1/mpich-4.1.tar.gz
tar zxf mpich-4.1.tar.gz
cd mpich-4.1/
#接着编辑编译选项, prefix为安装路径, cc\cxx\cf分别为c\c++\fortran的编译器
./configure --prefix=/home/tangzehao/MPI/install cc=gcc cxx=g++ fc=gfortran
# 开始编译和安装
make -j4
make install
# 安装完成后将mpich写入环境变量
vim ~/.bashrc
# 键入i,追加以下指令
export PATH=/home/tangzehao/MPI/install/bin:$PATH
# 按Esc退出,输入:wq保存退出,然后更新
source ~/.bashrc
安装完mpich再继续编译parmetis,进入该文件夹,找到文件夹里的build-parmetis.sh脚本,编辑它的一些预设,然后再运行之。
vim build-parmetis.sh
## 键入I 进入插入模式,修改以下指令
make config cc=mpiicc prefix=/es01/paratera/sce0588/zl/ASC/parmetis-4.0.3/parmetis-install
## 修改cc=gcc, 追加cxx=g++, 将prefix修改为文件所在路径下的安装目录,我将其修改为以下
make config cc=mpicc prefix=$QUEST1/parmetis-4.0.3/parmetis-install
## 接着按Esc键退出编辑,键入:wq保存并退出,然后运行之
sh build-parmetis.sh
3. 编译hypre
进入文件夹,找到build-hypre.sh脚本,编辑然后运行之。
vim build-hypre.sh
## 键入I 进入插入模式,修改以下指令
./configure --prefix="/es01/paratera/sce0588/zl/ASC/hypre-2.28.0/install" --with-MPI --enable-shared
## 将prefix修改为文件所在路径下的安装目录,我将其修改为以下
./configure --prefix="$QUEST1/hypre-2.28.0/install" --with-MPI --enable-shared
## 接着按Esc键退出编辑,键入:wq保存并退出,然后运行之
sh build-hypre.sh
4. 编译petsc
这里一开始我总是无法通过编译,后来结合其他文章做了一些修改³。
进入文件夹,找到build-petsc.sh脚本,对应的指令进行修改,然后运行之。
vim build-petsc.sh
## 键入I 进入插入模式
#!/bin/bash
## 这里将以下两条注释掉(虽然不知道是做什么的,但最终通过编译了(笑))
#source /es01/paratera/parasoft/module.sh
#source /es01/paratera/parasoft/oneAPI/2022.1/setvars.sh
module load cmake/3.17.1 gcc/7.3.0-para
# 这两条更改了路径到本文件夹,这两个变量后面还会再用到,应该与这里保持一致
export PETSC_DIR=$QUEST1/petsc-3.19.3
export PETSC_ARCH=petsc_install
./configure --with-mpi-dir=/home/tangzehao/MPI/install \ # 这里删掉了cc和cxx参数,改为使用mpich
--with-fortran-bindings=0 \
--with-hypre-dir=$QUEST1/hypre-2.28.0/install \ # 这里改为更改hypre所在目录
--with-debugging=0 \
COPTFLAGS="-O3" \
CXXOPTFLAGS="-O3" \
make -j 20 PETSC_DIR=$QUEST1/petsc-3.19.3 PETSC_ARCH=petsc_install all # 这两个变量和上面保存一致
make all check
## 接着按Esc键退出编辑,键入:wq保存并退出,然后运行之
sh build-petsc.sh
5. 编译petsc_solver
进入文件夹,找到build-petscsolver.sh脚本,对应的指令进行修改,然后运行之。
vim build-petscsolver.sh
## 键入I 进入插入模式
export CC=mpicc # 这两个改为mpi的编译器
export CXX=mpicxx
## 这两个lapack前缀改为自己所在的目录
export CPATH=$QUEST1/lapack-3.11/CBLAS/include:$QUEST1/lapack-3.11/LAPACKE/include:$CPATH
export LD_LIBRARY_PATH=$QUEST1/lapack-3.11:$LD_LIBRARY_PATH
## 接着按Esc键退出编辑,键入:wq保存并退出,然后运行之
sh build-petscsolver.sh
6. 编译OpenCAEPoro
进入文件夹,同样修改mpi-build-petsc.sh脚本,这里修改的地方比较多,都是把路径前缀改为自己的文件夹所在目录
vim mpi-build-petsc.sh
## 键入I 进入插入模式
export CC=mpicc # 这两个改为mpi的编译器
export CXX=mpicxx
## 路径前缀改为源文件所在路径,PETSC_DIR和PET_ARCH和前面保持一致
# users specific directory paths
export PARMETIS_DIR=$QUEST1/parmetis-4.0.3
export PARMETIS_BUILD_DIR=$QUEST1/parmetis-4.0.3/build/Linux-x86_64
export METIS_DIR=$QUEST1/parmetis-4.0.3/metis
export METIS_BUILD_DIR=$QUEST1/parmetis-4.0.3/build/Linux-x86_64
export PETSC_DIR=$QUEST1/petsc-3.19.3
export PETSC_ARCH=petsc_install
export PETSCSOLVER_DIR=$QUEST1/petsc_solver
export CPATH=$QUEST1/petsc-3.19.3/include/:$CPATH
export CPATH=$QUEST1/petsc-3.19.3/petsc_install/include/:$QUEST1/parmetis-4.0.3/metis/include:$QUEST1/parmetis-4.0.3/include:$CPATH
export CPATH=$QUEST1/lapack-3.11/CBLAS/include/:$CPATH
## 接着按Esc键退出编辑,键入:wq保存并退出,然后运行之
sh mpi-build-petsc.sh
7. 测试运行OpenCAEPoro
进入OpenCAEPoro文件夹,修改参数p为进程数量,执行以下指令
mpirun -n p ./testOpenCAEPoro ./data/test/test.data
如果安装成功,测试样例将会在终端打印出运行时间等信息,在文件夹./data/test下也会生成新文件:SUMMARY.out和FastReview.out,如果进程数大于1还会生成statistics.out文件。
三、样例运行与优化
在OpenCAEPoro文件夹运行baseline,指定进程数量p,查看样例的运行时间
mpirun -np p ./testOpenCAEPoro ./data/case1/case1.data verbose=1
1.改变进程数量
由于官方文档说明进程数不超过10,所以一开始我设置的进程数是10,最终Wall Time为1778秒左右,Object Time为228.992秒,花费了比较长的时间。
后面尝试了一下提高进程数,还是可行的,由于服务器处理器为32核64线程,这里进程数改为64再跑一遍,最终Wall Time为682秒左右,Object Time为52.403秒,增幅336%,提升很大。
2.多节点并发运行
原理也是提高进程数量,只不过这里在多台设备上同时运行,主打一个力大砖飞(乐)。这里涉及到MPI的多节点运行和ssh免密登录,参考了一些资料⁴。
- 首先需要设置节点间ssh免密登录,hosts文件已经配置好gpu01\gpu02\gpu03\gpu04的ip地址和主机名,可以直接使用主机名登录,在登录的主机生成公钥,由于服务器已经把文件都同步,每台主机同个用户上的文件都一样,因此不用再互相发送公钥,生成公钥后即可互相免密登录,同样也不需要每个可执行文件都拷贝到各主机上。
cd ~/.ssh
ssh-keygen -t rsa # 一路回车
cat ~/.ssh/id_rsa.pun >> ~/.ssh/authorized_keys
- 在用户目录新建一个配置文件hostfile,每个主机对应希望运行的进程数量,这里都设置为64
cd ~
vim hostfile
## 写入以下内容 ##
gpu01:64
gpu02:64
gpu03:64
gpu04:64
##
- 接着开始执行,这里把执行指令写成文件用bash指令运行,方便运行和调整
vim run
## 指令内容
NODE=4 # 结点数量
P=64 # 每个结点的进程数量,当然也可以每个结点都不一样,具体在hostfile中调整
NP=$[$NODE*$P] # 进程总数量,应该与hotsfile中所有结点加起来的进程数量一致
HOSTFILE=~/hostfile
cd /home/tangzehao/OpenCAEPoro/OpenCAEPoro/
mpirun -n $NP -machinefile $HOSTFILE ./testOpenCAEPoro ./data/case1/case1.data verbose=1 # 开始执行
##
bash run # 执行文件中的指令
在用2台主机各64进程测试时,发现运行时间非但没有减少,反而增加了许多,Wall Time来到了2600秒,而Object Time为76秒,甚至于我在中途利用gpu04跑64进程都比这个测试先结束(悲)。
我不是很理解为什么多台主机一起计算,速度下降了不少,不过考虑到主机间的通信和数据交换也需要时间,可能是各进程太多导致的,毕竟每个核心都吃满的话,数据交换也会变慢,也许是如此吧,于是我又用4台主机,每台主机60进程再跑了一遍,果然快了不少,最终Wall Time为783秒,而Object Time也降低到了29.382秒,相较于单机增幅78%,效果拔群啊(喜)。
按照这样的逻辑再思考一下,在gpu01发起的并发,会不会需要它用更多的时间收集通信,而其他主机需要的时间更少呢,那么可以让其他三台主机的进程数多些,但也不全吃满,64个进程分63个来计算应该就够了,而1号机保持60个进程,于是我把主机配置设置为如下
gpu01:60
gpu02:63
gpu03:63
gpu04:63
按照这样的配置运行,在计算到第500个左右(总共3600个)就花了将近1600秒,所以我知道是不会更快了,因此提前结束了计算。
这样的结果也促进了我进一步思考,在查看cpu占用率时,我发现在每台主机60个进程的情况下,4台主机的cpu占用率都在93%左右(当前用户),也就是说主机间的数据交换其实并不会占用太多运算量,1号主机并不需要更多的空闲算力来收集数据。而gpu03由于有其他用户在进行计算,cpu总占用率达到了96%,因此这里猜测是gpu03在使用63个进程时,算力达到瓶颈,才拖慢了总体进程。
不过感觉再多几个线程提升也不会很大了,没有太多时间等4台主机都空闲,这里就先这样吧。
3.修改一些编译选项
- 之前在编译parmetis时出过一些问题,在搜索时发现这个库可以根据系统设置编译选项¹,在parmetis/metis/include/metis.h 文件中,可以将IDXTYPEWIDTH和REALTYPEWIDTH从32修改为64,前者是int型变量的大小,后者决定浮点数使用单精度(32)还是双精度(64)。
#define IDXTYPEWIDTH 64
#define REALTYPEWIDTH 64
修改之后按照之前的步骤重新编译parmetis和OpenCAEPoro,在单机64进程的情况下,Wall Time为817秒左右,Object Time为55.211秒,相较于修改之前单机降幅5%,影响不大吧。
- 之前编译时,所有的编译器都是mpi,并没有使用intel oneAPI的编译,而官方文档提示使用该库会更好,因此可以将编译器都修改为oneAPI的编译器,c编译使用icx,c++编译使用icpx⁵。
## 这里只给出修改的地方
source opt/intel/oneapi/setvars.sh # 这里参考了https://blog.csdn.net/qq_41443388/article/details/124505277,也许本来已经可以用的,这里再手动更新一次。
# 这一条来自parmetis中的build-parmetis.sh脚本,将mpicc改为icx
make config cc=icx prefix=$QUEST1/parmetis-4.0.3/parmetis-install
# 这一条来自petsc中的build-petsc.sh脚本,将--with-mpi-dir修改为cc和cxx,指定编译器
./configure cc=icx cxx=icpx \
--with-fortran-bindings=0 \
--with-hypre-dir=$QUEST1/hypre-2.28.0/install \
--with-debugging=0 \
COPTFLAGS="-O3" \
CXXOPTFLAGS="-O3" \
# 这两条来自petsc_solver中的build-petscsolver.sh脚本,修改了编译器
# 另外,OpenCAEPoro中的mpi-build-petsc.sh也这样修改
export CC=icx
export CXX=icpx
完成上述修改后按照顺序重新编译各源文件,再重新编译OpenCAEPoro,运行样例,在单机64进程的情况下,Wall Time为539秒左右,Object Time为53.515秒,相较于之前的编译器,增幅3%,影响不大。多机4*60进程的情况下,Wall Time为352秒左右,Object Time为20.407秒,相较于编译选项修改之前,增幅43%,提升较大,也许oneAPI的编译器对多进程的优化更好。
4. 修改源代码
在修改代码前我想着换数学库的,但是这个东西的具体算法我也不了解,可能不同库的api不一样,最多就给一些库更新一些版本,所以还是看看代码有哪些地方能改的。
这里先用Vtune看一下各个函数在每个线程花费的时间,优化这些函数就行了,占用时间少的也没什么优化空间,不过这部分能修改的东西还是比较少,因为我也不知道这些函数具体实现什么功能,只能抠一抠细节了(难绷)。
运行一次用vtune查看一个进程的运行状态
在236秒捕获中,可修改的部分实际消耗的时间还是太短了,消耗最高的可修改部分是OCPFlux01::AssembleMatFIM,耗时1.587秒,占比不到0.7%,OCPFlux01::CalFlux耗时0.897秒,改一下这部分试试吧。
这部分代码变量太多了不好改,干脆把循环的i++都改成++i吧,后面的写法会快一点,但只有一点点。
另外,这里的if-else改成case也许会好点
## 原来的
if ((exbegin) && (exend)) {
rho[j] = (bvs.rho[bId_np_j] + bvs.rho[eId_np_j]) / 2;
}
else if (exbegin && (!exend)) {
rho[j] = bvs.rho[bId_np_j];
}
else if ((!exbegin) && (exend)) {
rho[j] = bvs.rho[eId_np_j];
}
else {
upblock[j] = bId;
rho[j] = 0;
flux_vj[j] = 0;
continue;
}
## 修改后
int sigma = exbegin + 2 * exend;
switch (sigma)
{
case 1:
rho[j] = bvs.rho[bId_np_j];
break;
case 2:
rho[j] = bvs.rho[eId_np_j];
break;
case 3:
rho[j] = (bvs.rho[bId_np_j] + bvs.rho[eId_np_j]) / 2;
break;
default:
upblock[j] = bId;
rho[j] = 0;
flux_vj[j] = 0;
continue;
}
重新编译后打算再跑一遍试试的,不过由于临近ddl,大家都在用服务器测试,没有空闲机器测试了,就没有再跑,先这样吧(叹气)
https://www.cnblogs.com/Orien/p/5919292.html
https://blog.csdn.net/x1131230123/article/details/129296417
https://blog.csdn.net/jiacong_wang/article/details/106723345
https://blog.csdn.net/liu_feng_zi_/article/details/108403321
https://blog.csdn.net/qq_41653433/article/details/135110603