Fortran - 函数与子程序性能比较

6
自几年前我完全不懂Fortran开始,我过度使用了没有参数的SUBROUTINE,以及共享数据,因此这些程序已经对通过USE语句可用的实际参数进行了计算。现在我需要重用其中一些过程(考虑计算体积中的散度,一个DIMENSION(:,:,:)大的数组,从该体积中的矢量场,3个DIMENSION(:,:,:)大的数组粘合在一起的派生类型),我希望要么保持它们是SUBROUTINE但删除USE语句并使用IN/OUT/INOUT虚拟参数(容易),要么将它们转换为FUNCTION(稍微困难一些,因为我需要学习一下)。
我想象中这两种方法可能会有性能上的差异,我想了解一下。在下面的MWE中,我编写了3个过程来执行相同的计算,但我不知道应该选择哪一个;我也不知道是否有其他更好的方法。
需要注意的是,我的程序中所有的三维实际数组都是可分配的,并且必须是这样的。
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程序中,我使用了几个三维大数组。这些数组相互作用,即对某些数组进行计算(不仅仅是点对点的+/-/*),以获得其他数组。考虑通过示例中的四种方法之一基于A计算出B,然后使用A = A + B来升级A本身的情况。如果我选择函数方法,那么我是否错误地认为上述4个选项可以完成相同的任务?在这个意义上,我可以简单地调用A = A + myfun(A)

编辑2 实际派生类型,以及那个大的三维数组,有半打其他字段(标量和小静态数组);此外,该类型的大多数变量都是数组,例如TYPE(blk3d),DIMENSION(n) :: A


“性能”也许不是现在需要担心的事情。这些子程序在参数的影响/要求方面意义不同。因此,在我们回答性能问题之前(不要忘记您更适合根据您的特定设置进行分析),您确定了解差异吗? - francescalus
@francescalus,我已经编辑了问题。也许在某些情况下,我应该用INOUT替换OUT以处理在过程外分配的数组?回答你的问题:不,这些差异对我来说并不清楚。我想做的显然更清楚,但是你的反对意见可以帮助我更深入地了解问题,并引导我重新思考问题。 - Enlico
@francescalus,我已经更新了问题,并提供了一个更加实际的例子(我担心它不再是“最小”的了,但这可能是它不过于简单的唯一方法)。 - Enlico
为什么要使用只有一个字段的类型?直接使用数组不就可以了吗?除非您计划在未来编写一些特定于类型的方法。 - John Alexiou
也许您的函数可以是elemental而非复制整个数组进行处理。 - John Alexiou
不,AB之间的实际关系远远超过了B = 2*A这个简单的代码行。 - Enlico
1个回答

5
选择是使用子程序还是函数通常应基于您将如何使用结果以及读者对结果的清晰度。
您要关注的是数据不必要地被复制的次数。对于大型数组,您会希望减少这种情况。
忽略程序中实际的工作,myfun将数据复制了第二次,并(可能)进行了两次分配。首先,函数结果变量被分配并将数据复制到其中。然后在调用程序中,如果B%values的形状尚未与结果相同,则会重新分配并再次复制数据,然后释放函数结果。
mysub和mysub2没有这个额外的分配/复制操作,而且基本上是等价的,但是调用mysub2可能需要一些额外的工作,例如在堆栈上设置描述符。如果子程序执行任何实际工作,我预计这会成为噪音。
选择mysub和mysub2取决于您在实际应用程序中的清晰度。除非您希望有一个这样的数组,否则拥有只有一个组件的派生类型似乎是不现实的。

谢谢你,@SteveLionel!我会对这些段落进行评论。(1) 在我看来,如果我使用FUNCTION,那么它会更清晰明了,但是清晰度并不是唯一的问题,因此有了这个问题。(2) 是的,我担心的是不必要的复制,因为这些数组可能非常大。(3) 所以,通过预分配B%values,我可以避免FUNCTION方法的第二次分配,对吗?如果这样做了,这种方法是否仍然比其他两种方法“劣”(正如我认为你暗示的那样)? - Enlico
“it” 在 “how clear is it” 中是什么意思?实际派生类型以及那个大的三维数组,还有其他半打的字段(标量和小的静态数组);此外,这种类型的大多数变量确实像您所设想的那样是数组(当我开始这个项目时,似乎这是模拟 MATLAB 的 cell 所使用的唯一方法,而我已经习惯/沉迷于使用它,现在看来这仍然是唯一的方法)。 - Enlico
1
这个函数的问题在于其语义是先对函数进行求值,然后将结果复制到赋值左侧的变量中,无论你事先如何操作都无法避免这种复制。无论你做什么,你都会有两个分配 - 一个用于函数结果,另一个用于变量中的数组。考虑到你所说的其他内容,我建议使用子程序,通过传递派生类型来引用或更新各个组件。 - Steve Lionel
1
“清晰”指的是如果其他人阅读代码,他们是否能够理解它?仅仅为了使用函数而使用函数是没有帮助的,如果代码的其余部分更加复杂。 - Steve Lionel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接