dyn1d/parallel_lmdz.F90

路径

LMDZ.MARS\libf\phymars\dyn1d\parallel_lmdz.F90

文件定位

dyn1d/parallel_lmdz.F90 在当前源码树中不是独立实现,而是一行相对路径:

../../../../LMDZ.COMMON/libf/dyn3dpar/parallel_lmdz.F90

它把 1D testphys1d 构建路径中的 parallel_lmdz 解析到公共并行动力运行时模块。目标模块负责 MPI/OpenMP 运行时状态、纬向带分配、halo 交换、broadcast/gather 和并行结束清理。当前 dyn1d 目录中直接使用该模块的源码点是 testphys1d.F90,只导入并调用 init_parallel

定义的符号

符号 类型 目标行号 作用
parallel_lmdz.F90 path stub dyn1d line 1 指向 LMDZ.COMMON/libf/dyn3dpar/parallel_lmdz.F90
parallel_lmdz module common line 4 保存并行运行状态并提供 MPI/OpenMP 分配、通信和结束入口。
using_mpi / using_omp logical, save common lines 13-14 记录当前编译/运行是否启用 MPI/OpenMP;init_parallelCPP_MPICPP_OMP 设置。
mpi_size / mpi_rank integer, save common lines 16-17 COMM_LMDZ 上的进程数和当前 rank。
jj_begin / jj_end / jj_nb integer, save common lines 18-20 当前 MPI rank 负责的纬向带起止和数量。
ij_begin / ij_end integer, save common lines 21-22 当前 rank 的扁平化水平索引范围,按 iip1jj_* 派生。
pole_nord / pole_sud logical, save common lines 23-24 当前 rank 是否持有北/南极纬向边界。
jj_begin_para(:) / jj_end_para(:) / jj_nb_para(:) allocatable integer, save common lines 26-28 每个 MPI rank 的纬向分配表。
OMP_CHUNK / omp_rank / omp_size integer, save common lines 29-31 OpenMP 垂直层 chunk 和线程编号/线程数;omp_rank 为 threadprivate。
jjb_u 等 dummy 变量 integer, save common lines 36-49 为兼容 dyn3d_mem/parallel_lmdz.F90 中的 calfis_loc 编译需求而保留的 u/v 网格占位变量。
init_parallel subroutine common line 53 初始化 MPI/OpenMP 状态、rank 纬向分配和输出文件。
SetDistrib subroutine common line 205 用外部传入的 jj_Nb_New 重设每个 rank 的纬向分配。
Finalize_parallel subroutine common line 238 释放分配表并按 XIOS/OASIS/MPI 条件关闭运行时。
Pack_Data / Unpack_Data subroutines common lines 297, 320 在二维 field 子块和一维通信 buffer 之间打包/解包。
barrier subroutine common line 345 using_mpi 时调用 MPI_Barrier(COMM_LMDZ)
exchange_hallo subroutine common line 361 按上下 halo 行数与极点边界执行相邻 rank 的非阻塞 halo 交换。
Gather_Field subroutine common line 494 把分布式 ip1jmp1ip1jm 水平场收集到指定 root rank。
AllGather_Field subroutine common line 578 Gather_Field(...,0),再从 rank 0 广播全场。
Broadcast_Field subroutine common line 600 从指定 rank 广播 field。

初始化流程

init_parallel 的源码流程如下:

  1. 按编译宏设置运行模式:定义 CPP_MPIusing_mpi=.TRUE.,否则 .FALSE.;定义 CPP_OMPusing_OMP=.TRUE.,否则 .FALSE.
  2. 调用 InitVampir 初始化性能计时钩子。
  3. using_mpi,用 mod_const_mpi 提供的 COMM_LMDZMPI_COMM_SIZEMPI_COMM_RANK;否则强制 mpi_size=1mpi_rank=0
  4. 如果 lunout 不是标准输入输出单元 5/6,非 root rank 打开 lmdz.out_XXXX 作为文本输出文件。
  5. 分配 jj_begin_para/jj_end_para/jj_nb_para(0:mpi_size-1)
  6. jjm+1 个纬向带尽量均分到所有 rank:每个 rank 先取整数商,前 MOD(jjm+1,mpi_size) 个 rank 多得一带。
  7. 若某 rank 的 jj_nb_para(i)<=1,打印错误;MPI 分支下调用 MPI_ABORT(COMM_LMDZ,-1,ierr)
  8. 由累计纬向带得到每个 rank 的 jj_begin_para(i)jj_end_para(i),再写入当前 rank 的 jj_begin/jj_end/jj_nb
  9. 计算扁平水平范围:ij_begin=(jj_begin-1)*iip1+1ij_end=jj_end*iip1
  10. 设置极点标志:mpi_rank==0 为北极 rank,mpi_rank==mpi_size-1 为南极 rank。
  11. OpenMP 分支中取 omp_size/omp_rank,默认 omp_chunk=ceil((llm+1)/omp_size),再用 getin('omp_chunk',omp_chunk) 允许配置覆盖;非 OpenMP 时设 omp_size=1omp_rank=0

通信和分布算法

SetDistrib

SetDistrib(jj_Nb_New) 接收一个从 0MPI_Size-1 的纬向带数量数组,直接覆写 jj_Nb_Para,然后从 rank 0 起逐段累加生成新的 jj_begin_para/jj_end_para。最后按当前 mpi_rank 重算 jj_begin/jj_end/jj_nb/ij_begin/ij_end

复现时要注意:这个例程不重新分配 jj_*_para,调用前必须已经执行过 init_parallel,且 jj_Nb_New 的大小必须与当前 MPI_Size 一致。

exchange_hallo

exchange_hallo(Field,ij,ll,up,down) 只在 using_mpi 为真时工作:

  1. 先执行全局 barrier,再调用 VTb(VThallo) 开始 halo 计时。
  2. 默认允许向上/向下发送和接收。
  3. 若当前 rank 是北极,禁止向上发送和接收;若是南极,禁止向下发送和接收。
  4. up==0,禁止向下发送和向上接收;若 down==0,禁止向上发送和向下接收。
  5. 每个启用的方向分配对应 buffer,Pack_Datarow*iip1*ll 个元素打包。
  6. MPI_ISSEND 把北侧/南侧边界发送给 mpi_rank-1mpi_rank+1,用 MPI_IRECV 接收相邻 rank 的边界。
  7. MPI_WAITALL 等待所有请求完成。
  8. Unpack_Data 把收到的 buffer 写回 Field(ij_begin-up*iip1,1)Field(ij_end+1,1) 位置。
  9. VTe(VThallo) 结束计时,再执行一次 barrier

Pack_DataUnpack_Data 的循环次序是外层垂直层 l=1..ll,内层水平点 i=1..row*iip1,因此通信 buffer 以“每层连续水平行块”的顺序排列。

Gather_FieldAllGather_FieldBroadcast_Field

Gather_Field(Field,ij,ll,rank) 支持两种水平数组长度:

每个 rank 先把本地纬向带打包到 Buffer_send。root rank 根据所有 jj_begin_para/jj_end_para 构造 Recv_countdispl,调 MPI_GATHERV 后再逐 rank Unpack_Data 回完整 Field。非 root rank 为避免 debug 模式未分配实参报警,会分配一个长度为 1 的 dummy Buffer_Recv

AllGather_FieldGather_Field(...,0)MPI_BCAST(Field,ij*ll,...)Broadcast_Field 则直接从指定 rank 广播整个 Field

结束流程

Finalize_parallel 先释放 jj_begin_para/jj_end_para/jj_nb_para。随后按编译宏和 type_ocean 选择关闭路径:

dyn1d 使用路径

testphys1d.F90 在程序开头导入:

use mod_const_mpi,       only: init_const_mpi
use parallel_lmdz,       only: init_parallel

随后在主流程早期顺序调用:

call init_const_mpi
call init_parallel

因此 1D 主程序依赖 mod_const_mpi 先建立 COMM_LMDZ,再由 parallel_lmdz::init_parallel 建立 rank/线程状态。当前 dyn1d 源码未发现直接调用 Finalize_parallelexchange_halloGather_FieldAllGather_FieldBroadcast_Field 的位置;这些例程来自公共并行模块,供完整动力/物理并行路径或被链接的公共代码使用。

编译条件和外部依赖

宏/依赖 影响
CPP_MPI 引入 mpif.h,启用 MPI_COMM_SIZE/RANKMPI_ABORTMPI_Barrier、非阻塞发送接收、MPI_GATHERVMPI_BCASTMPI_FINALIZE
CPP_OMP 使用 OpenMP runtime 取线程数和线程号,并设置 omp_chunk
CPP_IOIPSL 从 IOIPSL 使用 getin;否则使用本地 ioipsl_getincom::getin
CPP_XIOS Finalize_parallel 中调用 wxios_close()
CPP_COUPLE / CPP_OMCT Finalize_parallel 中走 OASIS/PRISM coupler 结束路径。
dimensions.h / paramet.h 提供 iip1/jjm/llm/ip1jmp1/ip1jm 等网格维度。
iniprint.h 提供 lunout 输出单元。
vampir 提供 InitVampirVTbVTeVThallo 计时标识。

复现要点

  1. 当前 dyn1d 文件本身只有一行路径;实际行为以 LMDZ.COMMON/libf/dyn3dpar/parallel_lmdz.F90 为准。
  2. init_parallel 假设 mod_const_mpi 已经初始化了 COMM_LMDZ;在 testphys1d 中源码顺序确实是先 init_const_mpiinit_parallel
  3. MPI 纬向分配要求每个 rank 至少两条纬向带,否则源码会 abort;这对小网格/多进程组合是硬约束。
  4. 非 MPI 编译时,using_mpi=.FALSE.mpi_size=1mpi_rank=0,通信例程主体不会执行。
  5. exchange_halloup/down 为 halo 行数;为 0 时源码会禁用对应方向的发送/接收,不是“交换 0 行后继续通信”。
  6. jjb_u/jje_u/...ijb_u/ije_u/... 在本目标文件内没有赋值逻辑;它们是为了公共接口兼容保留的 dummy 状态。

待确认

相关页面