Fortran - 函数与子例程性能

Fortran - Function vs subroutine performance

自从大约几年前我完全不熟悉 Fortran 以来,我过度使用 SUBROUTINEs 没有参数,连同共享数据,因此这些程序已经对实际参数进行了计算,可用通过 USE 语句。现在我需要重用其中的一些程序(考虑计算体积中的散度,一个 big DIMENSION(:,:,:) 数组,来自该体积中的矢量场,3 DIMENSION(:,:,:) 数组以派生类型粘合在一起),我想要

我想这两种方法的性能可能存在差异,我想了解这一点。在下面的 MWE 中,我写了 3 个程序来做同样的计算,但我不知道我应该如何选择一个或另一个;我也不知道其他方法是否更可取。

请注意,我程序中的所有 rank-3 实际数组都是 ALLOCATABLE 并且必须如此。

PROGRAM mymod

    IMPLICIT NONE

    TYPE blk3d
        REAL,    DIMENSION(:,:,:), ALLOCATABLE :: values
    END TYPE blk3d
    TYPE(blk3d) :: A, B

    INTEGER, PARAMETER :: n = 2
    INTEGER :: i

    ALLOCATE(A%values(n,n,n))
    A%values = RESHAPE([(i/2.0, i = 1, PRODUCT(SHAPE(A%values)))], SHAPE(A%values))
    print *, A%values

    ! 1st way
    B = myfun(A)
    print *, B%values

    DEALLOCATE(B%values)

    ! 2nd way
    ALLOCATE(B%values(n,n,n))
    CALL mysub(A, B)
    print *, B%values

    DEALLOCATE(B%values)

    ! 3rd way
    ALLOCATE(B%values(n,n,n))
    CALL mysub2(A, B%values)
    print *, B%values

CONTAINS

  FUNCTION myfun(Adummy) RESULT(Bdummy)                                                                                                              
    IMPLICIT NONE

    TYPE(blk3d), INTENT(IN) :: Adummy
    TYPE(blk3d)             :: Bdummy

    ALLOCATE(Bdummy%values, mold = Adummy%values)
    Bdummy%values(:,:,:) = 2*Adummy%values

  END FUNCTION myfun

  SUBROUTINE mysub(Adummy, Bdummy)

    IMPLICIT NONE

    TYPE(blk3d), INTENT(IN)    :: Adummy
    TYPE(blk3d), INTENT(INOUT) :: Bdummy

    Bdummy%values(:,:,:) = 2*Adummy%values

  END SUBROUTINE mysub

  SUBROUTINE mysub2(Adummy, Bdummy)

    IMPLICIT NONE

    TYPE(blk3d),            INTENT(IN)  :: Adummy
    REAL, DIMENSION(:,:,:), INTENT(OUT) :: Bdummy

    Bdummy(:,:,:) = 2*Adummy%values

  END SUBROUTINE mysub2

END PROGRAM mymod

编辑 在我做 CFD 的程序中,我使用了几个 rank-3 大数组。这些数组相互作用,因为对其中一些数组执行计算(不仅仅是逐点 +/-/*,...)以获得其他数组。想想 B,它是通过示例中的 4 个过程之一基于 A 计算的,然后用于通过 A = A + B 升级 A 本身。我认为上述 4 个选项可以完成相同的任务是错误的吗?从这个意义上说,如果我选择函数方法,我可以简单地调用 A = A + myfun(A)

EDIT2 实际的派生类型,连同那个大的 3 阶数组,还有六个其他字段(标量和小型静态数组);另外,大多数这种类型的变量都是数组,例如 TYPE(blk3d), DIMENSION(n) :: A.

在子例程或函数之间进行选择通常应基于您将如何使用结果以及结果对 reader 的清晰程度。

您真正关心的是数据被不必要地复制了多少次。对于大阵列,您可能希望减少它。

忽略过程中的实际工作,myfun 第二次复制数据并(可能)进行两次分配。首先分配函数结果变量并将数据复制到它。然后返回调用者,如果 B%values 与结果的形状不同,则重新分配 B%values 并再次复制数据,然后释放函数结果。

mysub 和 mysub2 没有这个额外的 allocate/copy 并且几乎是等效的,尽管对 mysub2 的调用可能需要一些额外的工作来在堆栈上设置描述符。如果子例程执行任何实际工作,我希望这会是噪音。

在 mysub 和 mysub2 之间做出选择实际上取决于它在您的实际应用程序中的清晰程度。只有一个组件的派生类型似乎是不现实的,除非您希望拥有这些组件的数组。