Fortran 与串行和附加并行运行相比,并行仿真在经过一些时间步后给出了不同的结果

Fortran 与串行和附加并行运行相比,并行仿真在经过一些时间步后给出了不同的结果,fortran,openmp,gnu,Fortran,Openmp,Gnu,我正在尝试使用OpenMP并行运行涡旋模拟的代码。这类似于粒子模拟,在每个时间步,下一个时间步的涡旋位置必须根据其速度计算,该速度由当前时间步所有其他涡旋的位置确定。一旦漩涡离开域,它们将被删除。我比较了并行版本代码和串行版本代码的每个时间步的漩涡数,并将每个版本运行多次 对于串行版本,涡流计数在每个时间步都精确匹配。对于并行情况,所有运行与串行情况匹配几十个时间步,之后,每个并行运行显示出差异,但与串行情况保持在7-10%的误差范围内(如下面的结果链接所示)。我知道这可能是因为在并行情况下,由

我正在尝试使用OpenMP并行运行涡旋模拟的代码。这类似于粒子模拟,在每个时间步,下一个时间步的涡旋位置必须根据其速度计算,该速度由当前时间步所有其他涡旋的位置确定。一旦漩涡离开域,它们将被删除。我比较了并行版本代码和串行版本代码的每个时间步的漩涡数,并将每个版本运行多次

对于串行版本,涡流计数在每个时间步都精确匹配。对于并行情况,所有运行与串行情况匹配几十个时间步,之后,每个并行运行显示出差异,但与串行情况保持在7-10%的误差范围内(如下面的结果链接所示)。我知道这可能是因为在并行情况下,由于不同线程之间的分布,计算步骤的顺序不同而产生舍入误差,但误差真的应该高达10%吗

我只在并行do构造中使用了reduction子句。整个代码中唯一的并行区域是函数
vblob()
,该函数位于我从主代码调用的模块内。
vblob()
中的所有函数调用都是
ixi()
fxi()
在此模块之外

function vblob(blobs,xj,gj)
    complex(8), intent(in) :: blobs(:,:), xj
    complex(8) :: delxi, delxic, di, gvic, xi
    real(8), intent(in) :: gj
    real(8) :: vblob(2)
    integer :: p

    gvic = 0.0; delxi = 0.0; delxic = 0.0; di = 0.0; xi = 0.0
    !$omp parallel do private(xi,delxic,delxi,di) shared(xj) reduction(+:gvic)
    do p = 1, size(blobs,1)
      xi = ixi(blobs(p,1))
      delxic = xj-conjg(xi)
      delxi = xj-xi
      di = del*fxi(xi)
      gvic = gvic + real(blobs(p,2))*1/delxic
      if (abs(delxi) .gt. 1.E-4) then
        gvic = gvic +  (-1)*real(blobs(p,2))*1/delxi
      end if
    end do
    !$omp end parallel do
    gvic = j*gvic*fxi(xj)/(2*pi)
    vblob(1) = real(gvic)
    vblob(2) = -imag(gvic)

  end function vblob
如果我构建并行代码的方式是错误的,那么错误应该从最初的几个时间步骤本身就出现了,对吗

(从图中可以看出,“BLOB”和“sheets”只是漩涡元素的类型,蓝线是全部元素。p和S分别代表并行和串行,R代表运行。实心绘图标记是串行代码,空心标记是并行代码的三个运行)

编辑:当我将变量的数值精度改为实数(4)时,结果的发散发生在比上述实数(8)更早的时间步。因此,这显然是一个全面的错误问题


TLDR:我想向在一系列时间步中看到这种结果的任何其他人澄清这一点,其中并行代码匹配前几个时间步,然后发散?

您的代码基本上总结了
gvic
中的许多术语。浮点运算是非关联的,也就是说,由于舍入的原因,
(a+b)+c
a+(b+c)
不同。此外,根据术语上的值和符号,每次操作可能会严重损失精度。有关此主题的真正强制性阅读,请参阅

当顺序循环计算时(没有给出聪明的编译器优化):

其中,
g_i
是通过迭代
i
gvic
添加的值,并行版本计算:

gvic = t_0 + t_1 + t_2 + ... t_(#threads-1)
其中
t_i
是线程
i
gvic
的累积私有值(OpenMP中的线程在Fortran中为0编号偶数)。未指定不同
t_i
s的减少顺序。OpenMP实现可以自由选择任何它认为合适的东西。即使所有
t_i
s按顺序求和,结果仍将与顺序循环计算的结果不同。不稳定的数值算法在并行时极易产生不同的结果

这是一件你很难完全避免的事情,但你要学会控制它,或者干脆忍受它的后果。在许多情况下,问题的数值解无论如何都是近似值。你应该关注守恒或统计性质。例如,遍历分子动力学模拟可能会并行产生完全不同的相轨迹,但总能量或热力学平均值等值将非常接近(除非存在严重的算法错误或非常糟糕的数值不稳定性)


旁注——当大多数CPU使用标准的32位和64位浮点运算时,您现在实际上很幸运地进入了这个字段。几年前,当x87还是一种东西时,浮点运算是以80位内部精度完成的,最终结果将取决于一个值离开并重新进入FPU寄存器的次数。

我想向在一系列时间步长内看到这种结果的任何人澄清这一点,并行代码在前几个时间步中匹配,然后发散?这是散度的一种定义,它从同一个角度开始,然后在一段时间后散开。我没有立即发现代码有任何错误,如果没有更多关于应用程序的知识,就很难说明它在任何意义上是否“错误”。但它的行为是在我的经验范围内(在HPC上的计算EM的十年或二十年)。。。。你可能想尝试一些代码的敏感性分析,也考虑到串行代码只是你的程序执行的潜在路径的整个空间中的一个样本,它不是(必要的)地面真理。它很适合与像你这样的有经验的人,马克。我是并行计算领域的新手。我知道,即使是串行代码也包含舍入错误,如果这与实验结果不匹配,并且具有合理的准确性,我将知道解决方案是错误的。但在您的经验中,您是否见过这样一种并行情况的解决方案,在这种情况下,错误会随着时间的推移而累积(在多次迭代中,每次迭代中的变量都是从上一次迭代中派生出来的)?我关于这可能是一个舍入错误问题的假设正确吗?再看看这张图片,你的想法是什么?但根据你的经验,你有没有看到过类似的解决方案,在这种情况下,错误会随着时间的推移而累积?是的,我关于这可能是一个舍入错误问题的假设是否正确?当然可以。再看图片,,
gvic = t_0 + t_1 + t_2 + ... t_(#threads-1)