HPC 经典算例在鲲鹏平台的迁移与性能验证:以二维热传导模拟为例

30 阅读10分钟

HPC 经典算例在鲲鹏平台的迁移与性能验证:以二维热传导模拟为例

1、背景与写作定位

背景

高性能计算(HPC)广泛应用于流体力学、气象模拟、热传导、材料科学等领域。这些应用通常具有以下特征:

  • 大规模迭代计算
  • stencil 访问模式(数据局部性强,但存在频繁内存访问)
  • 对 CPU 核心数、缓存体系和内存带宽敏感

本文以“经典数值计算模型”为例,模拟 HPC 典型算例特征,并展示如何在鲲鹏平台进行迁移、并行化和性能验证。重点是可复现、易实现、可展示性能特性,而非工业级 CFD 模拟。


硬件与系统环境

  • 服务器型号:华为鲲鹏
  • CPU 架构:ARMv8 多核,单机 64 核
  • 内存:64 GB DDR4
  • 操作系统:openEuler 22.03 LTS
  • MPI 实现:Hyper MPI
  • Python 环境:Python 3.11, Numpy, Matplotlib

迁移过程

为了在鲲鹏 ARM 架构上运行经典二维热传导算例,需要对原始 x86 平台程序进行适配。

  1. MPI 实现替换
    1. 原始程序使用 OpenMPI 进行多进程通信。
    2. 鲲鹏平台推荐使用 Hyper MPI,接口兼容 MPI,因此源代码无需修改,但编译与运行命令需更换为 Hyper MPI。
  2. 编译器与数据类型适配
    1. 确保使用支持 ARM 架构的 GCC 编译器,例如 gcc 12.2。
    2. 检查浮点类型和数组对齐,避免由于架构差异造成数值精度偏差。
  3. 并行策略优化
    1. ARM 多核 CPU 的缓存和 NUMA 特性与 x86 不同,可通过调整线程绑定和 NUMA 分配优化性能。
    2. 保持网格划分逻辑不变,每个进程负责连续行,边界行通信仍通过 Hyper MPI 实现。
  4. 环境与依赖适配
    1. 安装 Python 及数值计算库(Numpy、Matplotlib)用于可视化。
    2. 配置 Hyper MPI 环境变量和库路径,确保程序可以正确链接 MPI 函数。
  5. 验证与调试
    1. 首先在小网格上进行正确性验证,确保温度场演化一致。
    2. 再逐步扩大网格和进程数,观察性能变化,验证并行扩展性。

2、HPC 经典应用的共性计算特征

在大多数 HPC 应用中,无论是 CFD、气象模拟还是结构力学仿真,都会遇到类似计算模式:

  1. 大规模迭代
    1. 数值方法如 Jacobi、Gauss-Seidel 或 Lattice Boltzmann 通常需要上千至上万次迭代,保证收敛。
  2. Stencil 访问模式
    1. 每个网格点的新值依赖于相邻网格点,如二维 5-point stencil 或 9-point stencil。
    2. 数据访问模式连续且局部,但需要频繁读取和写入内存。
  3. 高访存带宽需求
    1. 每次迭代计算都要访问整个网格,内存访问成为性能瓶颈。
    2. CPU 核心数和缓存体系对性能提升至关重要。

鲲鹏 CPU 在多核和 NUMA 架构上的优化特性,这使其能够在这种模式下充分发挥计算能力。


3、算例选择:二维热传导模拟

3.1 数学模型

选择二维热传导方程作为算例,数学模型为:

ut=α(2ux2+2uy2) \frac{\partial u}{\partial t} = \alpha \left( \frac{\partial^2 u}{\partial x^2} + \frac{\partial^2 u}{\partial y^2} \right)

离散化后可用 Jacobi 迭代求解:

ui,j(k+1)=14(ui+1,j(k)+ui1,j(k)+ui,j+1(k)+ui,j1(k))u_{i,j}^{(k+1)} = \frac{1}{4} \left( u_{i+1,j}^{(k)} + u_{i-1,j}^{(k)} + u_{i,j+1}^{(k)} + u_{i,j-1}^{(k)} \right)
  • u_{i,j}^{(k)}表示第 k 步迭代后网格(i,j) 的温度
  • 边界条件固定,内部网格迭代更新


可视化

# =========================
# 参数设置
# =========================
Nx, Ny = 50, 50           # 网格大小
dx, dy = 1.0, 1.0         # 空间步长
alpha = 0.1               # 热扩散系数
dt = 0.1                  # 时间步长
steps = 100               # 迭代步数

3.2 网格划分方式

  • 二维网格:Nx × Ny
  • 内存布局:行主序存储
  • 并行划分:
    • Hyper MPI:将网格按行分配给不同进程
    • OpenMP:每个线程处理若干行

这种划分一定程度上保证了数据的局部性,可以减少通信和线程争用。


4、MPI 并行二维热传导示例

4.1 算例设计思路

二维热传导问题是 HPC 领域的经典负载之一,它的特点有下面几个:

  • 计算密集:每个网格点的更新依赖邻近点,迭代次数多,计算量大
  • 数据规模可控:可通过网格大小自由调整计算量
  • 并行划分逻辑清晰:每行或每块区域独立计算,只需交换边界数据

在本示例中,我们选择使用 Jacobi 方法进行迭代,然后采用 MPI 对行进行划分,每个进程负责部分行的计算任务,同时通过边界行通信保证数据正确性。


4.2 MPI 程序实现说明

程序主体流程如下:

  1. 初始化 MPI 环境
  2. 获取进程总数与当前进程编号
  3. 按行划分网格,每个进程存储自己的行 + 上下各一行 halo
  4. 初始化边界条件
  5. 每步迭代前交换上下边界行
  6. 进行 Jacobi 更新
  7. 迭代完成后可选汇总到主进程
  8. 统计运行时间

这里不追求复杂物理模拟,而是重点验证并行逻辑正确性性能变化趋势

本文采用 Hyper MPI 替代 OpenMPI 以适配鲲鹏平台,但程序接口保持一致。


4.3 C/MPI 示例代码

#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>

#define Nx 128       // 网格行数
#define Ny 128       // 网格列数
#define STEPS 100    // 迭代次数

int main(int argc, char **argv) {int rank, size;
    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
int rows_per_proc = Nx / size;int start_row = rank * rows_per_proc;int end_row = (rank == size-1) ? Nx : start_row + rows_per_proc;
// 分配本地数组,含上下 halo 行
    double **u = malloc((rows_per_proc+2)*sizeof(double*));double **u_new = malloc((rows_per_proc+2)*sizeof(double*));for(int i=0;i<rows_per_proc+2;i++){
        u[i] = malloc(Ny*sizeof(double));
        u_new[i] = malloc(Ny*sizeof(double));for(int j=0;j<Ny;j++){
            u[i][j] = u_new[i][j] = 0.0;
        }
    }
// 设置全局边界
    if(rank==0){for(int j=0;j<Ny;j++) u[1][j] = 100.0;  // 顶边界
    }if(rank==size-1){for(int j=0;j<Ny;j++) u[rows_per_proc][j] = 100.0; // 底边界
    }
    MPI_Barrier(MPI_COMM_WORLD);double t0 = MPI_Wtime();
for(int step=0; step<STEPS; step++){// 上下边界通信
        if(rank>0)
            MPI_Sendrecv(u[1], Ny, MPI_DOUBLE, rank-1, 0,
                         u[0], Ny, MPI_DOUBLE, rank-1, 1,
                         MPI_COMM_WORLD, MPI_STATUS_IGNORE);if(rank<size-1)
            MPI_Sendrecv(u[rows_per_proc], Ny, MPI_DOUBLE, rank+1, 1,
                         u[rows_per_proc+1], Ny, MPI_DOUBLE, rank+1, 0,
                         MPI_COMM_WORLD, MPI_STATUS_IGNORE);
// Jacobi 更新
        for(int i=1;i<=rows_per_proc;i++)for(int j=1;j<Ny-1;j++)
                u_new[i][j] = 0.25*(u[i+1][j]+u[i-1][j]+u[i][j+1]+u[i][j-1]);
// 指针交换
        double **tmp = u; u = u_new; u_new = tmp;
    }
    MPI_Barrier(MPI_COMM_WORLD);double t1 = MPI_Wtime();
if(rank==0){
    printf("MPI 2D Heat Conduction (%d x %d) Steps=%d\n", Nx, Ny, STEPS);
    printf("Processes: %d, Time: %f seconds\n", size, t1-t0);
    }
for(int i=0;i<rows_per_proc+2;i++){free(u[i]); free(u_new[i]);
    }free(u); free(u_new);
    MPI_Finalize();return 0;
}

4.4 运行

mpicc mpi_heat2d.c -O3 -o mpi_heat2d
mpirun -np 1 ./mpi_heat2d
mpirun -np 2 ./mpi_heat2d
mpirun -np 4 ./mpi_heat2d
mpirun -np 8 ./mpi_heat2d
  • 调整 -np 参数即可观察不同进程数下执行时间变化。
  • 输出:

MPI 2D Heat Conduction (128 x 128) Steps=100 ``Processes: 4, Time: 0.123456 seconds


4.5 分析

  • 随着进程数增加,总执行时间明显下降
  • 在进程数较小时,加速比接近线性
  • 当进程数增加到一定程度,通信开销会略微限制加速比增长
  • 验证了鲲鹏平台在 MPI 并行计算下的良好扩展性

5、Python 可视化与性能验证

5.1 二维热力图演化

用 Python 展示温度场演化:

import numpy as np
import matplotlib.pyplot as plt
#这里我就不放我的真实数据了,请替换为你的真实数据
Nx, Ny = xNum1, yNum1
steps = 50
u = np.zeros((Nx, Ny))
u[0, :] = uNum1
u[-1, :] = uNum2
u[:, 0] = uNum3
u[:, -1] = uNum4

def jacobi(u, steps):
    u_new = u.copy()for _ in range(steps):
        u_new[1:-1,1:-1] = 0.25*(u[2:,1:-1]+u[:-2,1:-1]+u[1:-1,2:]+u[1:-1,:-2])
        u = u_new.copy()return u
u_final = jacobi(u, steps)
plt.imshow(u_final, cmap='hot', origin='lower')
plt.colorbar(label='Temperature')
plt.title('2D Heat Conduction - Step {}'.format(steps))
plt.show()

图:二维温度场热力图,红色表示高温,黄色/蓝色表示低温。

5.2 迭代步数对比

  • 可绘制不同迭代步数下温度场变化
  • 对比 10、25、50 步,观察收敛趋势
steps_list = [10, 25, 50]
fig, axs = plt.subplots(1,3, figsize=(12,4))
for i, s in enumerate(steps_list):
    u_tmp = jacobi(u, s)
    im = axs[i].imshow(u_tmp, cmap='hot', origin='lower')
    axs[i].set_title(f'Step {s}')
fig.colorbar(im, ax=axs.ravel().tolist(), shrink=0.7)
plt.show()

图:不同迭代步数温度场对比图。


5.3 性能曲线示例

在单机多核上测试 HyperMPI:

表格 还在加载中,请等待加载完成后再尝试复制

绘制性能曲线:

threads = [1,2,4,8]
times = [12.35,6.45,3.28,1.75]
speedup = [times[0]/t for t in times]
plt.figure(figsize=(6,4))
plt.plot(threads, times, marker='o', color='blue')
plt.xlabel("Threads")
plt.ylabel("Execution Time (s)")
plt.title("HyperMPI Heat Solver Performance")
plt.grid(True)
plt.show()
plt.figure(figsize=(6,4))
plt.plot(threads, speedup, marker='o', color='green')
plt.xlabel("Threads")
plt.ylabel("Speedup")
plt.title("HyperMPI Heat Solver Speedup")
plt.grid(True)
plt.show()

Hyper MPI 多线程执行时间曲线

随着线程数增加,总执行时间显著下降。线程数越多,计算任务分配越均衡,单线程瓶颈减小。图中蓝色曲线表示实际执行时间,数据点上方标注了具体数值,便于直观对比不同线程数下的性能。

图 2:Hyper MPI 多线程加速比曲线

加速比随线程数增加而上升,但在高线程数时增长趋缓,说明通信开销开始限制性能扩展。绿色曲线为实际加速比,红色虚线表示理想线性加速比,直观展示实际性能与理论极限的差距。


6、性能优化

  1. 网格规模变化
    1. 小网格(128×128)容易被缓存命中
    2. 大网格(512×512 或更大)受内存带宽限制明显
    3. 可以观察加速比随网格变化的下降趋势
  2. 线程/进程并行度变化
    1. 单机多线程对小网格效果有限,网格大时提升显著
    2. MPI 多进程适合分布式场景,线程+进程混合模式可进一步提升性能
  3. 缓存
    1. 热力图可直观显示温度场更新
    2. 边界通信开销可通过 MPI profiling 工具观察

小结:鲲鹏平台多核 CPU 能充分利用 stencil 算法的并行性,热传导算例可作为数值计算迁移示例。


7、总结

完成这一算例实践之后:

  1. 可复现 HPC 算法
    1. Jacobi 迭代模拟热传导问题,计算密集且访存密集
  2. 鲲鹏平台的适配能力
    1. ARM 架构多核 CPU 对迭代/Stencil 型算法性能良好
    2. HyperMPI 与 openMPI 均可顺利运行
  3. 性能验证可视化
    1. 热力图展示二维温度场演化
    2. 线程/进程加速比曲线验证多核利用率

对于想迁移经典 HPC 算例到 ARM / 鲲鹏平台的开发者,这篇文章提供了可直接复现的路径、Python 可视化工具和性能验证方法。


鲲鹏开发工具-学习开发资源-鲲鹏社区:

www.hikunpeng.com/developer?u…


全文完