数组的导数应该在 CFD 代码中逐个数组还是逐个元素计算?

计算科学 有限差分 流体动力学 计算物理学
2021-12-17 08:03:07

我正在 Fortran 90 中制作我自己的有限差分计算磁流体动力学代码。查看其他代码,它们似乎可以计算它们的导数,它们的变量的bb,例如aa,如下所示:x

CALL system_clock(tstart, count_rate)
DO ix = 1, nx - 1
  ixp = ix + 1
  DO iy = 1, ny
    DO iz = 1, nz
      bb(ix,iy,iz) = (aa(ixp,iy,iz) - aa(ix,iy,iz)) / dx
    END DO
  END DO
END DO
CALL system_clock(tstop, count_rate)
PRINT*, "Computation time =", REAL(tstop - tstart) / REAL(count_rate)

换句话说,他们逐个元素地计算导数。另一种计算导数的方法是逐个数组,即:

CALL system_clock(tstart, count_rate)
bb(1:nx-1,:,:) = (aa(2:nx,:,:) - aa(1:nx-1,:,:)) / dx
CALL system_clock(tstop, count_rate)
PRINT*, "Computation time =", REAL(tstop - tstart) / REAL(count_rate)

第二种方法似乎快了大约 37 倍。那么为什么我没有看到更多的代码逐个数组而不是逐个元素地计算它们的 -derivatives 数组呢?逐个数组的缺点是什么,我缺少什么吗?x

1个回答

从算法的角度来看,这两个版本实际上是相同的。不同之处在于,Fortran90 非常擅长大型数组运算,并且有非常成熟的编译器可用于此类运算。在您的第二个示例中,编译器可以更自由地优化效率我不是编译器内部工作的专家,但可以这样想:当您编写时:

b(1:nx-1,:,:) = (aa(2:nx,:,:) - aa(1:nx-1,:,:)) / dx

然后你告诉你的编译器你想对所有以“:”为目标的索引执行这个操作。您还可以告诉它您不关心操作的执行顺序。然后编译器可以针对您的特定架构进行优化、重新安排操作、进行智能预取等。

在您的第一个示例中,您在呈现操作的方式上更加具体。您告诉它它应该以行/列为主进行迭代,执行此操作的顺序必须是您所说的方式。

简而言之: 在 Fortran 中,大多数情况下,您可以从给予编译器合理的自由中获益!

为什么很少有人以这种方式使用它?好吧,这在很大程度上取决于您要优化的内容。如果人们有两天时间来实现一个原型,那么这些细节可能并不重要。有些人可能会发现第一个版本更具表现力和可读性等。有些人可能对编译器如何优化您的指令只有初步的了解。您通过实际测试和比较做出了完全正确的决定,因为这通常归结为。

(注意索引溢出!可能并不总是有左/右邻居来区分。)