1. PETSc 体系结构
PETSc(Portable, Extensible Toolkit for Scientific Computation)是 Argonne 国家实验室开发的大规模并行科学计算库,以 C 编写,提供 C/C++/Fortran/Python 绑定。其核心分层如下:
┌─────────────────────────────────────────────┐
│ Application / SLEPc / TS / TAO │ // 特征值/时间步/优化
├─────────────────────────────────────────────┤
│ SNES (非线性求解器:Newton-Krylov, Picard) │
├─────────────────────────────────────────────┤
│ KSP (Krylov 子空间 + 预处理子系统) │
├─────────────────────────────────────────────┤
│ PC (预处理器:ILU, AMG, Jacobi, SOR) │
├─────────────────────────────────────────────┤
│ Mat (稀疏矩阵:AIJ, Block AIJ, Dense, SBAIJ) │
│ Vec (分布式向量) │
├─────────────────────────────────────────────┤
│ DM (分布式网格:DMDA, DMPlex, DMStag) │
├─────────────────────────────────────────────┤
│ MPI / CUDA / Kokkos / OpenCL │ // 后端并行运行时
└─────────────────────────────────────────────┘
| 核心对象 | 作用 | 类比 |
|---|---|---|
| Vec | 分布式稠密向量 | NumPy ndarray / std::vector |
| Mat | 稀疏/稠密矩阵 | scipy.sparse / Eigen::SparseMatrix |
| KSP | 线性系统迭代求解器 | SciPy cg / gmres |
| SNES | 非线性方程组求解 | Newton-Raphson 封装 |
| PC | 预处理上下文 | ilu / amg wrapper |
| DM | 网格离散化管理 | Fenics/COMSOL 网格层 |
2. 稀疏矩阵格式
PETSc 默认采用压缩稀疏行格式(CSR),在内部称为 AIJ(Adjacency IJ)。对于块结构(如多物理场问题),可使用 BAIJ 提升局部性和缓存效率。
| 格式 | 存储方式 | 适用场景 |
|---|---|---|
| MATSEQAIJ / MATMPIAIJ | CSR,每行独立存储 | 通用稀疏矩阵 |
| MATSEQBAIJ / MATMPIBAIJ | 块 CSR,block size = bs | PDE 块系统(如多块流体) |
| MATSEQSBAIJ | 对称块 CSR | 对称正定矩阵 |
| MATAIJSELL / MATCUSPARSE | GPU 友好格式 | CUDA 后端 |
创建与预分配矩阵:
#include <petsc.h>
PetscErrorCode CreateLaplacian2D(MPI_Comm comm, PetscInt N, Mat *A) {
Mat mat;
PetscInt n = N * N; // 总自由度
PetscInt rank, size;
PetscInt istart, iend;
MPI_Comm_rank(comm, &rank);
MPI_Comm_size(comm, &size);
// 行范围划分
istart = rank * (n / size);
iend = (rank == size - 1) ? n : (rank + 1) * (n / size);
MatCreate(comm, &mat);
MatSetSizes(mat, iend - istart, iend - istart, n, n);
MatSetType(mat, MATAIJ); // 自动选择 SEQ or MPI
MatMPIAIJSetPreallocation(mat, 5, NULL, 2, NULL); // 5 非零/行(本地),2 非零/行(远端)
MatSetOption(mat, MAT_NEW_NONZERO_LOCATIONS, PETSC_TRUE);
// 遍历本地行,填充 5 点 stencil
for (PetscInt idx = istart; idx < iend; idx++) {
PetscInt i = idx % N;
PetscInt j = idx / N;
PetscScalar v = -1.0;
PetscScalar center = 4.0;
// 中心点
MatSetValue(mat, idx, idx, center, INSERT_VALUES);
// 邻居:上下左右
if (i > 0) MatSetValue(mat, idx, idx - 1, v, INSERT_VALUES);
if (i < N-1) MatSetValue(mat, idx, idx + 1, v, INSERT_VALUES);
if (j > 0) MatSetValue(mat, idx, idx - N, v, INSERT_VALUES);
if (j < N-1) MatSetValue(mat, idx, idx + N, v, INSERT_VALUES);
}
MatAssemblyBegin(mat, MAT_FINAL_ASSEMBLY);
MatAssemblyEnd(mat, MAT_FINAL_ASSEMBLY);
*A = mat;
return 0;
}
关键贴士:
- 预先调用
MatMPIAIJSetPreallocation告知 PETSc 每行非零元数量,避免动态内存重分配。 - 组装分两阶段:
MatAssemblyBegin触发非零元通信交换,MatAssemblyEnd完成矩阵结构定型。
3. 线性求解器选择
KSP 层封装了 Krylov 子空间 solver + 预处理器系统。通过运行时选项(无需重编译)即可切换算法:
KSP ksp;
Vec x, b;
KSPCreate(PETSC_COMM_WORLD, &ksp);
KSPSetOperators(ksp, A, A); // 系统矩阵与预处理器矩阵
// 设置求解器类型(也可通过命令行 -ksp_type cg)
KSPSetType(ksp, KSPCG); // 对称正定问题用 CG
// KSPSetType(ksp, KSPGMRES); // 非对称用 GMRES
// KSPSetType(ksp, KSPBCGS); // 非对称替代:BiCGSTAB
// 设置预处理
KSPGetPC(ksp, &pc);
PCSetType(pc, PCJACOBI); // 简单并行可用 Jacobi
// PCSetType(pc, PCILU); // 串行/小并行有效
// PCSetType(pc, PCHYPRE); // BoomerAMG,大规模并行推荐
KSPSetFromOptions(ksp); // 覆盖:优先采用命令行参数
KSPSolve(ksp, b, x);
常用solver选型矩阵:
| 矩阵性质 | 推荐 KSP | 推荐 PC | 说明 |
|---|---|---|---|
| SPD (对称正定) | CG | AMG (Hypre/BoomerAMG), ICC | CG 在每步仅需 1 次矩阵向量积 |
| 对称不定 | MINRES | AMG, ILU(0) | MINRES 对不定矩阵稳定 |
| 非对称 | GMRES(30) / BiCGSTAB | ILU, AMG | GMRES 需 restart,内存增长 |
| 鞍点问题 | GMRES + 块预处理 | PCFIELDSPLIT | Stokes/Navier-Stokes |
| 病态近奇异 | GMRES + 直接法预条件 | PCLU (KLU/SuperLU) | 小子问题直接求解 |
4. 并行组装策略
PETSc 的 Mat 天然支持 MPI 分布式存储。每进程仅持有本地行,非本进程的矩阵元素通过调用 MatSetValue 自动被路由到目标进程。无需手动管理 MPI send/recv。
对于有限元/有限体积程序,推荐采用 DMDA( Distributed Array) 自动处理网格分区和矩阵模式:
DM da;
DMDACreate2d(PETSC_COMM_WORLD,
DM_BOUNDARY_NONE, DM_BOUNDARY_NONE, // 边界条件
DMDA_STENCIL_STAR, // 5 点 stencil
N, N, PETSC_DECIDE, PETSC_DECIDE, // 全局网格
1, 2, NULL, NULL, &da); // 自由度 1, 重叠层数 2
DMSetFromOptions(da);
DMSetUp(da);
// 从 DM 创建矩阵,PETSc 自动处理并行分布
DMCreateMatrix(da, &A);
// 通过 DMDA 遍历本地网格点,填充矩阵
5. 非线性求解器 SNES
对于非线性方程 $F(x) = 0$,SNES 自动构造 Newton 迭代:
SNES snes;
Mat J; // Jacobian 矩阵
Vec r; // 残差向量
SNESCreate(PETSC_COMM_WORLD, &snes);
SNESSetFunction(snes, r, FormFunction, &user_ctx); // 用户提供 F(x)
SNESSetJacobian(snes, J, J, FormJacobian, &user_ctx); // 用户提供 J = dF/dx
SNESSetType(snes, SNESNEWTONLS); // 牛顿法 + 线搜索
SNESSetFromOptions(snes);
SNESSolve(snes, NULL, x);
// 可在运行时切换:-snes_type ksponly (不求 Jacobian,纯线性迭代)
若解析 Jacobian 推导困难,可启用有限差分近似 -snes_fd 或着色后的稀疏有限差分 -snes_mf_operator。
6. 性能剖析
PETSc 内置 PetscLog 系统,无需外部工具即可定位瓶颈。
# 运行并输出详细计时日志
mpiexec -n 64 ./poisson -log_view :poisson.log
# 通过 PETSc 内置 event 标记自定义代码段
PetscLogEvent event;
PetscLogEventRegister("MyStencilCompute", 0, &event);
PetscLogEventBegin(event, 0, 0, 0, 0);
// ... 自定义计算 ...
PetscLogEventEnd(event, 0, 0, 0, 0);
典型日志输出解读:
Event Count Time (sec) Flop/s
--- --- --- --- --- --- --- --- --- --- --- --- --- ---
MatMult 200 1.234e+00 8.5e+09 // 矩阵向量积
KSPSolve 10 5.678e+00 --- // 总线性求解时间
PCSetUp 1 2.100e-01 --- // 预处理器设置
VecScatter 220 3.400e-01 --- // 向量通信开销
若 VecScatter 时间占比高,说明负载不均或 halo 通信过密,应考虑网格分区优化或提升 *overlap 层数。若 PCSetUp 过高,可考虑矩阵结构复用(仅数值变化时设置 MatSetOption(A, MAT_REUSE_MATRIX, PETSC_TRUE))。
PETSc 将 PDE 求解的繁琐并行细节封装在对象层次之下,程序员聚焦数学建模,底层自动映射到 MPI/GPU/Kokkos。从矩阵组装到 Krylov 求解,掌握 PETSc 的核心对象生命周期与选项系统,是跨平台高效科学计算的关键。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。