Fortran+MPI并行计算结果随无关代码行变动问题排查
Fortran+MPI并行计算结果随无关代码行异常变动问题
问题现象
使用Fortran+mpi开展并行计算时,出现异常现象:增删代码中与核心计算逻辑完全无关的语句行时,计算结果会发生剧烈变化。
整套代码共分为三个部分:
- 主程序
IntColl.f90:包含代码的并行执行片段; - 模块
m.f90:包含实现特定计算逻辑的大量函数,其中某函数内存在增删后会直接影响计算结果的代码行; - 模块
Constants.f90:存储各类常量定义。
代码总规模约2000行,以下仅展示与问题关联度最高的代码片段。
主程序并行代码片段
CN为进程总数,主程序中并行计算相关代码如下:
DO counter1 = 1, NG IF ( MOD ( counter1, CN ) .EQ. RANK ) THEN YDOT ( counter1 ) = FACTOR * ABt ( dmgw ( counter1 ) ) + & FACTOR1 * CIng ( dmgw ( counter1 ) ) YDOT ( counter1 + NG ) = FACTOR * ABMt ( dmgw ( counter1 ) ) END IF END DO DO counter = 1, ( CN - 1 ) IF ( RANK .EQ. counter ) THEN CALL MPI_SEND ( & YDOT ( counter ), & 1, & MPI_DP_VEC, & 0, & 1, & MPI_COMM_WORLD, & IERROR & ) END IF IF ( RANK .EQ. 0 ) THEN CALL MPI_RECV ( & YDOT ( counter ), & 1, & MPI_DP_VEC, & counter, & 1, & MPI_COMM_WORLD, & MPI_STATUS_IGNORE, & IERROR & ) END IF END DO CALL MPI_BCAST ( & YDOT ( 1 ), & 2 * NG, & MPI_DOUBLE_PRECISION, & RANK, & MPI_COMM_WORLD, & IERROR & )
CIng函数代码片段
并行代码段调用的、来自m.f90模块的CIng ( p )函数代码如下:
REAL ( DP ) FUNCTION CIng ( p ) ! Collisional integral for \nu + \gamma IMPLICIT NONE INTEGER :: i, CTR REAL ( DP ) :: p ! p is coordinate momentum of initial neutrino: nu ( p ) + gamma ( k ) --> nu ( q ) + e^- ( q1 ) + e^+ ( q2 ) REAL ( DP ) :: const_A REAL ( DP ) :: const_i REAL ( DP ) :: const REAL ( DP ) :: a, b REAL ( DP ) :: z1, z2, z3, z4, z5, z6, z7 i = 0 CTR = 0 const_A = 128.D+00 * PI * FSC * G_F ** 2 * ( M_E / X ) ** 2 const_i = ( DSQRT ( YMAX ** 2 + X ** 2 ) - X ) ** 2 * YMAX / 2.D+00 ** 8 / PI ** 5 / p * ( M_E / X ) ** 3 const = const_A * const_i a = 0.D+00 z1 = 0.D+00 z2 = 0.D+00 z3 = 0.D+00 z4 = 0.D+00 z5 = 0.D+00 z6 = 0.D+00 z7 = 0.D+00 DO i = 1, NS b = 0.D+00 z1 = x1 ( i ) z2 = x2 ( i ) z3 = x3 ( i ) z4 = x4 ( i ) z5 = x5 ( i ) z6 = x6 ( i ) z7 = x7 ( i ) b = UndInt ( p, z1, z2, z3, z4, z5, z6, z7 ) a = a + b IF ( b .NE. 0.D+00 ) THEN CTR = CTR + 1 WRITE ( 9, "(2ES23.15)" ) x2 ( i ), x3 ( i ) ! This #1 END IF END DO print *, p, CTR, 'q' ! This #2 CIng = const * a / NS END FUNCTION CIng
具体异常表现
当注释掉标注为This #1和This #2的两行I/O代码时,得到的计算结果与保留这两行时的结果存在灾难性差异;只要保留这两行中任意一行,得到的计算结果就符合预期的“正确结果”特征:
- 正确结果(保留任意一行I/O代码、编译优化等级为
-O3):输出为某物理量依赖关系,数据点落在分段光滑曲线上,符合理论预期的分段光滑特性; - 错误结果(注释掉两行I/O代码):输出点呈现无规律噪声特征,无明确依赖关系。
补充背景信息
- 函数
CIng ( p )会调用另一函数UndInt ( p, z1, z2, z3, z4, z5 ,z6, z7 ),后者会进一步调用其他下游函数,所有被调函数均仅实现数值计算逻辑,无特殊操作。 - 数组
x1 ( i )、x2 ( i )等在IntColl.f90中从文件读取数值完成初始化,这些数组声明于Constants.f90模块,主程序和m.f90模块均引用了该常量模块。 - 计算结果与编译优化等级
-O<n>相关:当This #1和This #2两行均被注释时,任意优化等级下得到的都是错误结果。 - 编译时添加
-Wall和-Wextra选项,输出大量两类警告:
这些警告大部分来自程序中使用的第三方Fortran库函数(例如DLSODA)。<variable> may be uninitialized in this function Inequality comparison for REAL(8) at (1) - 编译时添加
-fbacktrace -fcheck=all -g选项时,即使保留This #1和This #2中的任意一行,也会得到错误结果。 - 当
counter1从1遍历到NG(NG取值为96)时,CIng ( dmgw ( counter1 ) )的计算耗时单调递增。
运行环境
- 代码标准:Fortran90
- 编译命令:
mpif90.mpich *.f90 *.f -O3 -o <程序名> - 运行命令:
mpirun -np <进程数> ./<程序名> - 系统环境:Ubuntu 18.04.04 LTS,
mpirun版本为3.3a2,编译器版本为GNU Fortran (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0。
问题根因与修复
最终排查确认该问题与多进程并行完全无关,根源是程序中存在执行路径上可能未初始化的变量。m.f90模块中的UndInt_ ( ... )函数包含内部变量a和b,二者的赋值逻辑位于多层复杂嵌套IF条件分支内,若不在声明后立刻初始化,部分执行场景下这两个变量不会被赋值,会存储内存栈中的“脏数据”。
存在问题的函数代码如下,修复方式为声明变量后立刻将a、b初始化为0.0:
REAL ( DP ) FUNCTION UndInt_ ( p, k, eps1, eps2, cosTh1, cosTh2, phi1, phi2 ) IMPLICIT NONE REAL ( DP ), INTENT ( IN ) :: p, k, eps1, eps2, cosTh1, cosTh2, phi1, phi2 REAL ( DP ) :: q1 ! q1 为电子坐标动量(电子动量乘以 X/M_E ) REAL ( DP ) :: q2 ! q2 为正电子坐标动量 REAL ( DP ) :: sinTh1 REAL ( DP ) :: sinTh2 REAL ( DP ) :: cosph1 REAL ( DP ) :: cosph2 REAL ( DP ) :: cosThk ! 电子极角余弦值 REAL ( DP ) :: q ! 出射中微子坐标动量绝对值 REAL ( DP ) :: a, b a = 0.0 ! 修复点:声明后立刻初始化 b = 0.0 ! 修复点:声明后立刻初始化 q1 = DSQRT ( eps1 ** 2 - X ** 2 ) q2 = DSQRT ( eps2 ** 2 - X ** 2 ) sinTh1 = DSQRT ( 1.D+00 - cosTh1 ** 2 ) sinTh2 = DSQRT ( 1.D+00 - cosTh2 ** 2 ) cosph1 = DCOS ( phi1 ) cosph2 = DCOS ( phi2 ) IF ( ( k ** 2 * ( q1 * cosTh1 + q2 * cosTh2 - p ) ** 2 + & k ** 2 * ( q1 * sinTh1 * cosph1 + q2 * sinTh2 * cosph2 ) ** 2 & ) .LT. & ( p * ( k - eps1 - eps2 ) - k * ( eps1 + eps2 ) + eps1 * eps2 + X ** 2 + & p * q1 * cosTh1 + p * q2 * cosTh2 - & q1 * q2 * ( sinTh1 * sinTh2 * DCOS ( phi1 - phi2 ) + cosTh1 * cosTh2 ) & ) ** 2 & ) THEN a = 0.0 b = 0.0 ELSE IF ( ( cosThk1 ( p, k, eps1, eps2, cosTh1, cosTh2, phi1, phi2 ) .GT. 1.D+00 ) & .OR. ( cosThk1 ( p, k, eps1, eps2, cosTh1, cosTh2, phi1, phi2 ) .LT. -1.D+00 ) & .AND. ( cosThk2 ( p, k, eps1, eps2, cosTh1, cosTh2, phi1, phi2 ) .GT. 1.D+00 ) & .OR. ( cosThk2 ( p, k, eps1, eps2, cosTh1, cosTh2, phi1, phi2 ) .LT. -1.D+00 ) & ) THEN a = 0.0 b = 0.0 ELSE IF ( ( DABS ( p + k - eps1 - eps2 - & q_1 ( p, k, eps1, eps2, cosTh1, cosTh2, phi1, phi2 ) & ) / & ( p + k - eps1 - eps2 ) & ) .LT. 1.D-015 & ) THEN cosThk = cosThk1 ( p, k, eps1, eps2, cosTh1, cosTh2, phi1, phi2 ) q = q_1 ( p, k, eps1, eps2, cosTh1, cosTh2, phi1, phi2 ) a = UndInt1 ( p, k, q, eps1, eps2, cosThk, cosTh1, cosTh2, phi1, phi2 ) END IF IF ( ( DABS ( p + k - eps1 - eps2 - & q_2 ( p, k, eps1, eps2, cosTh1, cosTh2, phi1, phi2 ) & ) / & ( p + k - eps1 - eps2 ) & ) .LT. 1.D-015 .AND. & ( DABS ( & ( cosThk1 ( p, k, eps1, eps2, cosTh1, cosTh2, phi1, phi2 ) - & cosThk2 ( p, k, eps1, eps2, cosTh1, cosTh2, phi1, phi2 ) ) / & cosThk1 ( p, k, eps1, eps2, cosTh1, cosTh2, phi1, phi2 ) & ) & ) .GT. 1.D-015 & ) THEN cosThk = cosThk2 ( p, k, eps1, eps2, cosTh1, cosTh2, phi1, phi2 ) q = q_2 ( p, k, eps1, eps2, cosTh1, cosTh2, phi1, phi2 ) b = UndInt1 ( p, k, q, eps1, eps2, cosThk, cosTh1, cosTh2, phi1, phi2 ) END IF END IF END IF UndInt_ = a + b END FUNCTION UndInt_
原因说明
函数UndInt_ ( ... )的返回值由a和b相加得到,未初始化时变量中的垃圾值会直接传入返回值,最终导致计算结果异常。增删无关I/O语句会改变栈内存布局,恰好覆盖或暴露了脏数据问题,才出现了结果随无关代码行变动的现象。
编译时开启-fbacktrace -fcheck=all -g运行时检查选项可快速定位此类bug,编码过程中需严格遵守变量初始化规范,不要忽略编译器给出的未初始化变量警告。
内容的提问来源于stack exchange,提问作者Vlad Yurchenko
相关产品推荐
相关产品推荐

