calfis_p 并行 transfer 架构
输入范围
dynphy_lonlat\calfis_p.F: L1-1520(并行物理调用接口)
phy_common\mod_phys_lmdz_para.F90: L1-78(MPI/OMP 分布初始化)
phy_common\mod_phys_lmdz_mpi_data.F90: L1-220(MPI 分布表)
phy_common\mod_phys_lmdz_omp_data.F90: L1-126(OMP 线程列分布)
phy_common\mod_phys_lmdz_transfert_para.F90(MPI+OMP 统一 transfer 封装)
phy_common\physics_distribution_mod.F90(物理分布初始化链)
Mars 运行参与度:条件经过。Mars 并行动力启用物理时(apphys 为真),calfis_p 在 MPI rank 和 OpenMP 线程两层执行完整的 scatter → call_physiq → gather 流程。
例程定位
本页聚焦 calfis_p 中 MPI 与 OpenMP 两层并行的数据搬运机制,以及它与 phy_common transfer 基础设施的关系。场映射和 tendency 回写的逐场细节已由 calfis-p-grid-mapping 和 calfis-p-physiq-contract 覆盖。
两层并行数据流
calfis_p 的并行数据搬运分为三个层次:
动力网格 (iip1,jjp1,llm)
↓ scatter (OMP DO / MASTER, klon=klon_mpi)
rank 级物理列 z*(klon_mpi, llm, ...)
↓ OMP scatter (手动拷贝, offset=klon_omp_begin-1)
线程级物理列 *_omp(klon_omp, llm, ...)
↓ call_physiq (nsplit_phys 循环)
线程级 tendency *_omp(klon_omp, llm, ...)
↓ OMP gather (手动拷贝回)
rank 级 tendency z*(klon_mpi, llm, ...)
↓ MPI wind exchange (手写 ISSEND/IRECV)
扩展数组 zdufi2/zdvfi2(klon_mpi+iim, llm)
↓ gather (OMP DO / MASTER)
动力网格 tendency pdufi/pdvfi/pdhfi/pdqfi/pdpsfi
编译守卫
calfis_p 的并行实现依赖三层预处理守卫:
| 守卫 | 控制范围 | Mars 并行动力 |
|---|---|---|
CPP_PARA |
parallel_lmdz(omp_chunk、AllGather_Field、jj_begin/jj_end)、Write_Field、Times、cpdet_mod |
启用 |
CPP_PHYS |
dimphy、mod_phys_lmdz_mpi_data、mod_phys_lmdz_omp_data、mod_interface_dyn_phys、callphysiq_mod |
启用 |
CPP_MPI |
mpif.h、MPI_Status、MPI_ISSEND/IRECV/WAITALL |
启用 |
如果没有 CPP_PHYS,源码打印 calfis_p: for now can only work with parallel physics 并 stop。CPP_MPI 控制手写 wind exchange 内的 MPI 调用;非 MPI 构建跳过整段交换。
rank 级 scatter:OMP 指令模式
rank 级 scatter 把动力二维数组转为 klon_mpi 物理列,全程使用 klon=klon_mpi。OMP 指令遵循固定模式:
标量场逐层 scatter(zplev、zpk、zteta、zqfi、zplay):
c$OMP DO SCHEDULE(STATIC,OMP_CHUNK)
DO l = 1, llm
do ig0 = 1, klon
i = index_i(ig0); j = index_j(ig0)
zpk(ig0,l) = ppk(i,j,l)
enddo
ENDO
c$OMP END DO NOWAIT所有线程共享 index_i/index_j(非 THREADPRIVATE),但写入 z* 数组的不同 ig0 位置,因此不存在竞争。NOWAIT 允许后续独立 scatter 立即启动。
MASTER 独占段(zpsrf、timer、moyzon):
c$OMP MASTER
do ig0 = 1, klon
zpsrf(ig0) = pps(index_i(ig0), index_j(ig0))
enddo
c$OMP END MASTERzpsrf scatter 放在 MASTER 段是因为后续 OMP DO 隐式 barrier 会等待 master 完成。
BARRIER 同步点:scatter 完成后、进入 OMP 线程级切片前有一个显式 c$OMP BARRIER(L752),确保所有线程看到完整的 rank 级 z* 数组。
OMP 线程级 scatter/gather
first_omp 分配
每个 OpenMP 线程首次进入时(first_omp 标志,THREADPRIVATE),按 klon_omp 分配 *_omp 工作区:
c$OMP BARRIER
if (first_omp) then
klon = klon_omp
allocate(zplev_omp(klon, llm+1))
allocate(zplay_omp(klon, llm))
! ... 共 20 个 allocate ...
allocate(zdqfic_omp(klon, llm, nqtot))
allocate(flxwfi_omp(klon, llm))
first_omp = .false.
endiffirst_omp 是 THREADPRIVATE,因此每个线程独立判断和分配。分配完成后所有线程的 *_omp 数组维度为 klon_omp(可能不同线程列数不同,余数优先给低编号线程)。
手动 scatter
BARRIER 后,源码把 klon 改为 klon_omp,设置 offset = klon_omp_begin - 1,然后逐数组手动拷贝:
klon = klon_omp
offset = klon_omp_begin - 1
do l = 1, llm+1
do i = 1, klon
zplev_omp(i, l) = zplev(offset+i, l)
enddo
enddo被 scatter 到 *_omp 的数组共 14 个:zplev_omp、zplay_omp、zpk_omp、zphi_omp、zphis_omp、presnivs_omp、zufi_omp、zvfi_omp、zrfi_omp、ztfi_omp、zqfi_omp、zdufi_omp、zdvfi_omp、zdtfi_omp、zdqfi_omp。
这不是 phy_common 的 scatter_omp 泛型调用,而是手写循环。原因可能是 calfis_p 在 CPP_PARA+CPP_PHYS 内编译,直接依赖物理列数组布局,而 scatter_omp 泛型接口不一定覆盖所有维度和类型组合。
手动 gather
physics 调用后(包括 nsplit_phys 循环和 tendency 平均),源码把 *_omp 结果拷贝回 rank 级 z*:
do l = 1, llm
do i = 1, klon
zufi(offset+i, l) = zufi_omp(i, l)
enddo
enddogather 回 z* 的数组共 13 个(含 zdpsrf)。之后 klon 恢复为 klon_mpi,500 CONTINUE 标签后 BARRIER 同步。
MPI wind exchange
physics 结束后,calfis_p 在 using_mpi 为真时执行一段手写 MPI 交换,这是 calfis_p 唯一直接使用 MPI 调用的段落:
发送阶段
IF (using_mpi) THEN
if (MPI_rank > 0) then
c$OMP DO SCHEDULE(STATIC,OMP_CHUNK)
DO l = 1, llm
du_send(1:iim, l) = zdufi(1:iim, l)
dv_send(1:iim, l) = zdvfi(1:iim, l)
ENDDO
c$OMP END DO NOWAIT
c$OMP BARRIER
c$OMP MASTER
!$OMP CRITICAL (MPI)
call MPI_ISSEND(du_send, iim*llm, MPI_REAL8, MPI_Rank-1, 401, ...)
call MPI_ISSEND(dv_send, iim*llm, MPI_REAL8, MPI_Rank-1, 402, ...)
!$OMP END CRITICAL (MPI)
c$OMP END MASTER
c$OMP BARRIER
endifrank > 0 的 rank 把前 iim 列 zdufi/zdvfi 打包发送给 rank-1。打包用 OMP DO 并行化,发送用 MASTER + CRITICAL(MPI) 保护。
接收阶段
if (MPI_rank < MPI_Size-1) then
c$OMP BARRIER
c$OMP MASTER
!$OMP CRITICAL (MPI)
call MPI_IRECV(du_recv, iim*llm, MPI_REAL8, MPI_Rank+1, 401, ...)
call MPI_IRECV(dv_recv, iim*llm, MPI_REAL8, MPI_Rank+1, 402, ...)
!$OMP END CRITICAL (MPI)
c$OMP END MASTER
endif
c$OMP BARRIERrank < size-1 从 rank+1 接收。BARRIER 确保所有线程在 WAITALL 前同步。
等待和组装
c$OMP MASTER
!$OMP CRITICAL (MPI)
if (MPI_rank>0 .and. MPI_rank<MPI_Size-1) then
call MPI_WAITALL(4, Req(1), Status, ierr)
else if (MPI_rank>0) then
call MPI_WAITALL(2, Req(1), Status, ierr)
else if (MPI_rank<MPI_Size-1) then
call MPI_WAITALL(2, Req(3), Status, ierr)
endif
!$OMP END CRITICAL (MPI)
c$OMP END MASTER
c$OMP BARRIER
ENDIF ! using_mpi中间 rank 等待 4 个 request(2 发 + 2 收),边界 rank 等待 2 个。
扩展数组构造
c$OMP DO SCHEDULE(STATIC,OMP_CHUNK)
DO l = 1, llm
zdufi2(1:klon, l) = zdufi(1:klon, l)
zdufi2(klon+1:klon+iim, l) = du_recv(1:iim, l)
zdvfi2(1:klon, l) = zdvfi(1:klon, l)
zdvfi2(klon+1:klon+iim, l) = dv_recv(1:iim, l)
! 同时清零 jj_begin / jj_end tendency ...
ENDDO
c$OMP END DO NOWAIT这段同时完成三件事:构造扩展数组、清零边界纬圈 tendency、为后续 gather 做准备。OMP DO 并行化层循环。
与 phy_common transfer 基础设施的关系
直接使用的 phy_common 符号
| 来源模块 | 符号 | calfis_p 中的用途 |
|---|---|---|
mod_phys_lmdz_mpi_data |
mpi_master(别名 mpi_root_xx) |
引入但未在主线直接使用 |
mod_phys_lmdz_mpi_data |
MPI_rank, MPI_Size, is_north_pole_dyn, is_south_pole_dyn |
rank/pole 判断 |
mod_phys_lmdz_omp_data |
klon_omp, klon_omp_begin |
线程列切片 |
mod_const_mpi |
COMM_LMDZ |
MPI wind exchange communicator |
parallel_lmdz |
omp_chunk, using_mpi, AllGather_Field, jj_begin/jj_end |
OMP chunk、MPI 标志、全局场收集 |
不直接使用的 phy_common transfer
calfis_p 不调用 mod_phys_lmdz_transfert_para 的 bcast/scatter/gather 泛型,也不调用 mod_phys_lmdz_mpi_transfert 或 mod_phys_lmdz_omp_transfert 的任何过程。原因是:
scatter 路径不同:
phy_common的scatter_mpi/scatter_omp在klon_glo(全局物理列)和klon_mpi/klon_omp之间搬运,使用klon_mpi_para_begin/end分布表。calfis_p的 scatter 是从动力(iip1,jjp1)二维格点到klon_mpi一维列,由index_i/index_j驱动,两者映射方式不同。gather 路径不同:
phy_common的gather_mpi从klon_mpi收集回klon_glo。calfis_p的 gather 是从klon_mpi一维列写回(iip1,jjp1)二维动力格点,由gr_fi_dyn_p和手动回写完成。wind exchange 独有:MPI wind exchange 是
calfis_p特有的跨 rank 通信,服务于pdvfi跨纬带 stencil。phy_common的 MPI transfer 族不包含这类自定义 stencil 交换。
AllGather_Field:Titan 专用
AllGather_Field 来自 parallel_lmdz,不是 phy_common transfer。它只在 Titan 分支(planet_type=="titan")和 flag_moyzon 为真时使用,把整个二维动力场收集到所有 rank,用于计算 zonal/global mean 剖面。Mars 主线不经过这段。
if (planet_type.eq."titan") then
call AllGather_Field(pp, iip1*jjp1, llmp1)
call AllGather_Field(pteta, iip1*jjp1, llm)
! ... 计算 plevmoy, tmoy, tetamoy, phimoy 等全局均值 ...
endif运行时并行守卫
除了编译守卫,calfis_p 在运行时还有多层条件分支:
| 守卫 | 类型 | 控制范围 |
|---|---|---|
using_mpi |
logical | MPI wind exchange 整段(L1182-1241) |
is_north_pole_dyn |
logical | scatter 极点 kstart=2、极点 wind 投影、gather 极点行扩展 |
is_south_pole_dyn |
logical | scatter 极点 kend=klon-1、极点 wind 投影、jj_end 不清零 |
firstcal |
SAVE logical | 首次调用分配、debut 标志 |
first_omp |
THREADPRIVATE logical | 每个线程首次 _omp 分配 |
flag_moyzon |
logical | Titan/Venus zonal mean 分支(moyzon_ch/moyzon_mu) |
MPI_rank |
integer | wind exchange 发送/接收边界判断 |
THREADPRIVATE 清单
calfis_p 声明的 THREADPRIVATE 变量分为两组:
OMP 工作区(20 个数组 + 1 个标志):
c$OMP THREADPRIVATE(zplev_omp, zplay_omp, zpk_omp, zphi_omp, zphis_omp,
c$OMP+ presnivs_omp, zufi_omp, zvfi_omp, ztfi_omp,
c$OMP+ zrfi_omp, zqfi_omp, zdufi_omp, zdvfi_omp,
c$OMP+ zdtfi_omp, zdqfi_omp, zdpsrf_omp, flxwfi_omp,
c$OMP+ zdufic_omp, zdvfic_omp, zdtfic_omp, zdqfic_omp)控制标志(3 个):
c$OMP THREADPRIVATE(first_omp)
c$OMP THREADPRIVATE(firstcal, debut)firstcal 和 debut 都是 THREADPRIVATE,这意味着每个线程独立维护首次调用状态。首次调用初始化在 OMP MASTER 段完成 rank 级分配,first_omp 控制线程级分配。
SAVE 工作区生命周期
rank 级 z* 数组使用 ALLOCATABLE, SAVE:首次调用时分配,后续调用复用同一内存。线程级 *_omp 数组同理,但每个线程独立分配。
du_send/dv_send/du_recv/dv_recv 是固定维度 (iim, llm) 的 SAVE 数组,不需要分配。zdufi2/zdvfi2 是 ALLOCATABLE, SAVE,维度 klon+iim,首次调用时分配。
与串行 calfis 的并行差异
| 方面 | 串行 calfis | 并行 calfis_p |
|---|---|---|
| 域 | 全局 (iip1,jjp1) |
本 rank (jj_begin:jj_end) |
| 列索引 | ngridmx 全局物理列 |
klon_mpi 本地物理列 |
| OMP | 无 | 两层:rank 级 OMP DO + 线程级 klon_omp |
| MPI | 无 | wind exchange(ISSEND/IRECV) |
| 极点 | 直接包含在 ngridmx 中 | 由 is_north/south_pole_dyn 控制 |
| AllGather | 无 | Titan 专用 |
| 工作区 | 固定维度 | ALLOCATABLE, SAVE |
| 编译守卫 | 无 CPP_PARA/CPP_PHYS | CPP_PARA + CPP_PHYS + CPP_MPI |
复现要点
calfis_p的 OMP scatter/gather 是手写循环,不是phy_common的scatter_omp/gather_omp调用。调试时不能用phy_commontransfer 的 buffer/barrier 语义来推理calfis_p的数据搬运。- MPI wind exchange 使用
COMM_LMDZ(动力 communicator),不是COMM_LMDZ_PHY(物理 communicator)。两者可能在某些配置中不同。 CRITICAL(MPI)保护 MPI 调用,但不保护du_send/dv_send的打包。打包在OMP DO中完成,BARRIER 后才由 MASTER 发送。如果去掉 BARRIER,MASTER 可能在打包完成前发送不完整数据。first_omp的 THREADPRIVATE 语义意味着不同线程可能在不同调用中完成首次分配。如果klon_omp在运行中变化(理论上不会),已分配的数组不会自动调整。- rank 级
z*数组的 OMP DO scatter 中,index_i/index_j是模块级共享数组(非 THREADPRIVATE),所有线程读取同一映射表但写入不同ig0位置,无竞争。 - 最后一 rank 的
du_recv/dv_recv未接收数据(MPI_rank==MPI_Size-1不进入接收分支),zdufi2(klon+1:)保持旧值。但pdvfi回写时is_south_pole_dyn分支覆盖了最后一 rank 的极点处理。
相关页面
- calfis_p 模块页
- calfis_p 动力格点到物理列映射
- calfis_p 与 call_physiq 接口约定
- calfis_p 日期与行星分支
- leapfrog-p-physics-coupling
- mod_phys_lmdz_para
- MPI data/transfer
- OMP data/transfer
- physics_distribution_mod
- parallel-transfer-6d
- leapfrog_p 分布和 halo 调度
- serial-parallel-dynamics
- phys-transfer-mpi-core
- phys-transfer-omp-core
待确认
COMM_LMDZ和COMM_LMDZ_PHY在 Mars 并行配置中是否为同一 communicator。using_mpi标志在纯 OpenMP(无 MPI)构建中是否始终为假,此时 wind exchange 是否完全跳过。calfis_p不调用phy_commontransfer 泛型是设计选择还是历史遗留;未来是否可能统一。