OpenCAEPoro的编译
可使用linux系统内火狐浏览器前往https://github.com/OpenCAEPlus/OpenCAEPoro_ASC2024将文件下载到家目录。开始按照文件的特殊顺序解压包。(如果没有解压zip文件的插件可以用以下代码)
git clone https://github.com/OpenCAEPlus/OpenCAEPoro_ASC2024
然后将下载好的压缩包进行解压操作
tar -xzf lapack-3.11.tar.gz
tar -xzf parmetis-4.0.3.tar.gz
tar -xzf hypre-2.28.0.tar.gz
tar -xzf petsc-3.19.3.tar.gz
tar -xzf petsc_solver.tar.gz
tar -xzf OpenCAEPoro.tar.gz
安装Lapack
进入lapack目录
cd lapack-3.11
编译lapack库
make blaslib
make cblaslib
make lapacklib
make lapackelib
查看文件状态
ls -lh *.a
所有库都编译完成
安装parmetis
进入parmetis目录
cd parmetis-4.0.3
readme中第二步为在脚本 build-parmetis.sh
中修改路径,在该脚本中找到并更改 make config
命令中的 prefix
参数,以指向正确的安装目录。
为了简化步骤,在此直接将ROOT_DIR添加至环境变量中
export ROOT_DIR=/home/pengziqiao/OpenCAEPoro_ASC2024
打开脚本文件进行修改
vim build-parmetis.sh
找到prefix那一行将/parmetis-4.0.3/parmetis-install前面的替换为ROOT_DIR(按i进入编辑模式,改完后esc退出编辑模式再:wq保存退出)
开始安装
sh build-parmetis.sh
出现报错,CMake 在尝试配置项目时找不到指定的 C 编译器 mpiicc
和 C++ 编译器 mpicxx
。
再仔细看一遍群里发的pdf,发现准备工作未完成,还未激活相关的套件。
source /opt/intel/oneapi/setvars.sh
在输入运行代码,编译完成。
安装hypre
进入 hypre-2.28.0目录
cd hypre-2.28.0
同上,进入脚本中修改路径。
vim build-hypre.sh
找到configure一行,将prefix后hypre-2.28.0之前的内容替换为ROOT_DIR
输入安装程序
sh build-hypre.sh
检查是否编译完成
ls -R /home/pengziqiao/OpenCAEPoro_ASC2024/hypre-2.28.0/install
安装petsc
进入petsc-3.19.3目录
cd petsc-3.19.3
同上在脚本中进行修改。
vim build-petsc.sh
在输入以下代码
./configure CC=mpiicc CXX=mpiicpc \
--with-fortran-bindings=0 \
--with-hypre-dir=ROOT_DIR/hypre-2.28.0/install \
--with-debugging=0 \
COPTFLAGS="-O3" \
CXXOPTFLAGS="-O3" \
参数含义:
CC
和 CXX
分别设置为 mpiicc和 mpiicpc
,这些通常是 MPI 编译器的名称。
--withfortran=0
选项,指示配置脚本不包含 Fortran 绑定。
--with-hypre-dir=ROOT_DIR/hypre-2.28.0/install \,制定安装路径
--with-debugging=0
选项指示编译过程不包含调试信息,这有助于生成更小、更快的二进制文件。
COPTFLAGS
和 CXXOPTFLAGS
设置了 -O3
优化标志,这通常用于启用最高级别的编译优化。
make
命令并行编译一个项目,同时指定了 PETSC_DIR
和 PETSC_ARCH
环境变量。
make -j 20 PETSC_DIR=$ROOT_DIR/petsc-3.19.3 PETSC_ARCH=arch-linux-c-opt all
make -j 20
:这个命令告诉 make
工具同时运行多达 20 个并行作业。PETSC_DIR=$ROOT_DIR/petsc-3.19.3
:这里你设置了 PETSC_DIR
环境变量,指向 PETSc。
PETSC_ARCH=arch-linux-c-opt
:这里你设置了 PETSC_ARCH
环境变量,这通常用于指定 PETSc 的构建配置。
再测试一下是否工作正常。
make PETSC_DIR=/home/pengziqiao/OpenCAEPoro_ASC2024/petsc-3.19.3 PETSC_ARCH=arch-linux-c-opt all
输入执行脚本指令
sh build-petsc.sh
编译完成
安装petsc_solver
进入petsc_solver目录
cd petsc_solver
修改build-petscsolver.sh文件中的路径为指定的ROOT_DIR
vim build-petscsolver.sh
修改CMakeLists.txt文件中的18和19行路径为/home/pengziqiao/OpenCAEPoro_ASC2024
执行安装指令
sh build-petscsolver.sh
安装OpenCAEPoro
进入OpenCAEPoro目录
cd OpenCAEPoro
在mpi-build-petsc.sh文件中修改路径为ROOT_DIR
运行mpi-build-petsc.sh文件
sh mpi-build-petsc.sh
测试安装
返回到OpenCAEPoro_ASC2024目录下,运行测试。
cd OpenCAEPoro
`mpirun -n p ./testOpenCAEPoro ./data/test/test.data`
其中p是并行运行的进程数,并且这个数字应该小于等于10.
mpirun -n 5 ./testOpenCAEPoro ./data/test/test.data
这里取p为5,得出结果。
测试完毕。
优化
改变进程数
针对case1进行优化,我们先看看case1的obejec time
mpirun -np 5 ./testOpenCAEPoro ./data/case1/case1.data verbose=1
再试试大一点的进程数10
mpirun -np 10
./testOpenCAEPoro ./data/case1/case1.data verbose=1
试试超过10如20
多机运行
为实现多机的并行运算,需要创建一个hostfile。
vim hostfile
然后列出所有参与计算的机器的IP地址和要在上面运行的进程数。
图
再输入运行指令,在原来代码基础上machinefile即可。(依然选取规定的最大值10)
cd OpenCAEPoro
mpirun -np 10 -machinefile /home/pengziqiao/OpenCAEPoro_ASC2024/hostfile ./testOpenCAEPoro ./data/case1/case1.data verbose=1
在多机运行的情况下甚至比单机运行的更慢,可能是由于MPI初始化。在MPI程序中,初始化MPI环境(MPI_Init
)可能需要一定的时间,如果任务本身运行时间较短,这个初始化时间就可能占据了相当一部分的运行时间
总结一下规律就是进程数越大,能显著地缩减object time。多机运行并不一定比单机运行快。
vtune查看热点
然后再具体看看某一部分的耗时,用 intel 套件中的 vtune 来收集程序运行信息,获得程序运行的热点图,找到耗
时久的函数。
首先配置环境
source /opt/intel/oneapi/vtune/2024.0/vtune-vars.sh
再运行程序。
vtune -collect hotspots -result-dir=/home/pengziqiao/OpenCAEPoro_ASC2024/OpenCAEPoro/results /home/pengziqiao/OpenCAEPoro_ASC2024/OpenCAEPoro/testOpenCAEPoro
最后得到一个csv文件。
无法查看,根据报错可能是由于ssh连接到服务器但没开启X11转发
ssh -X pengziqiao@hostname
export PATH=$PATH:/opt/intel/oneapi/vtune/2024.0/bin64/vtune-gui
vtune-gui /home/pengziqiao/OpenCAEPoro_ASC2024/OpenCAEPoro/results_case1.gpu01/results_case1.gpu01.vtune
在src目录下进入vim,输入以下指令查找热点程序。
:vimgrep /dl_init/ **/*.cpp
图形化界面有bug,还是以命令行界面的为准。
vtune -collect hotspots -result-dir /path/to/vtune/results mpirun -np 10 ./testOpenCAEPoro ./data/case1/case1.data verbose=1
vtune -report hotspots -r /path/to/results.vtune
得到以下结果
性能数据显示,libpetsc.so.3.19
:PETSc 库、libHYPRE-2.28.0.so
:HYPRE 库消耗时间最长。
[Unknown]:表示性能分析工具无法识别或解析函数的名称。
先找到libpetsc文件的位置,可用以下指令。
grep -r "libpetsc.so.3.19.3"
发现不在修改范围内,尝试查询其他函数,也都不在范围内,没什么帮助。
输入输出的加速
默认情况下,C++ 标准库的 cin、cout与 C 的 scanf、
printf
是同步的。这意味着,每次进行 I/O 操作时,C++ 会等待 I/O 操作完成,这可能会降低性能,特别是在需要大量 I/O 操作的情况下。
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
我们查找OpenCAEPoro/src/* 中全部代码文件以及petsc_solver/src/PET SC_FIM_solver.cpp 中标明的可修改的代码段哪里有cin和cout,使用以下指令。
grep -r "std::cin" --include="*.cpp" /home/pengziqiao/OpenCAEPoro_ASC2024/OpenCAEPoro/src
grep -r "std::cout" --include="*.cpp" /home/pengziqiao/OpenCAEPoro_ASC2024/OpenCAEPoro/src
在 UtilMath.cpp
文件中,有一个注释行提到了 std::cout
。
在 UtilTiming.cpp
文件中,有三行代码使用了 std::cout
来输出信息,包括时间成本。
在 GmshGrid.cpp
文件中,有一行代码使用了 std::cout
来输出模型的维度信息。
同样在petsc_solver/src/PET SC_FIM_solver.cpp进行如上操作。
grep -r "std::cin" --include="*.cpp" /home/pengziqiao/OpenCAEPoro_ASC2024/OpenCAEPoro/src
grep -r "std::cout" --include="*.cpp" /home/pengziqiao/OpenCAEPoro_ASC2024/OpenCAEPoro/src
优化结果如下:
从结果来看只提升0.12%,可以当作误差了。
前人的经验分享
ASC24 OpenCAEPoro 齐鲁工业大学优化方案 | sethome的橱窗 个人博客
根据上面这位老哥的分享,可以知道OpenCAEPoro中的分区处理Partition.cpp文件使用线性搜索来查找send_buffer中的条目,极大地浪费了时间。将代码改为用哈希表来查找信息能极大地提高效率
vector<vector<idx_t>> send_buffer;
vector<vector<idx_t>>& recv_buffer = elementCSR;
recv_buffer.push_back(vector<idx_t> {myrank, 0, 0});
unordered_map<idx_t, idx_t> ump_send_buffer;
for (idx_t i = 0; i < numElementLocal; i++) {
if (part[i] == myrank) {
recv_buffer[0][1] ++;
recv_buffer[0][2] += (xadj[i + 1] - xadj[i]);
}
else {
// euphoria
if(ump_send_buffer[part[i]]){
idx_t k = ump_send_buffer[part[i]] - 1;
send_buffer[k][1] ++;
send_buffer[k][2] += (xadj[i + 1] - xadj[i]);
}
else{
send_buffer.push_back(vector<idx_t>{part[i], 1, xadj[i + 1] - xadj[i]});
ump_send_buffer[part[i]] = send_buffer.size();
}
}
}
同样地将下面一处线性搜索的地方改为哈希表。
if(ump_send_buffer[part[i]]){
idx_t s = ump_send_buffer[part[i]] - 1;
send_buffer[s][send_buffer_ptr[s * 4 + 0]++] = i + vtxdist[myrank];
send_buffer[s][send_buffer_ptr[s * 4 + 1]] = send_buffer[s][send_buffer_ptr[s * 4 + 1]++] + xadj[i + 1] - xadj[i];
copy(&adjncy[xadj[i]], &adjncy[xadj[i + 1]], &send_buffer[s][send_buffer_ptr[s * 4 + 2]]);
copy(&adjproc[xadj[i]], &adjproc[xadj[i + 1]], &send_buffer[s][send_buffer_ptr[s * 4 + 3]]);
send_buffer_ptr[s * 4 + 2] += xadj[i + 1] - xadj[i];
send_buffer_ptr[s * 4 + 3] += xadj[i + 1] - xadj[i];
}
}
结果不尽如人意,object time反而较于未优化时增加了。n
对比优化前后,assembling这一项明显增加了,有可能是冲突太多了,达不到O(1)的优化,退化为O(n)。