Fortran - 函数与子例程性能
Fortran - Function vs subroutine performance
自从大约几年前我完全不熟悉 Fortran 以来,我过度使用 SUBROUTINE
s 没有参数,连同共享数据,因此这些程序已经对实际参数进行了计算,可用通过 USE
语句。现在我需要重用其中的一些程序(考虑计算体积中的散度,一个 big DIMENSION(:,:,:)
数组,来自该体积中的矢量场,3 大 DIMENSION(:,:,:)
数组以派生类型粘合在一起),我想要
- 保留它们
SUBROUTINE
但删除 USE
语句并使用 IN
/OUT
/INOUT
虚拟参数(简单),或
- 在
FUNCTION
秒内转换它们(稍微难一点,因为我要学习一点)
我想这两种方法的性能可能存在差异,我想了解这一点。在下面的 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 之间做出选择实际上取决于它在您的实际应用程序中的清晰程度。只有一个组件的派生类型似乎是不现实的,除非您希望拥有这些组件的数组。
自从大约几年前我完全不熟悉 Fortran 以来,我过度使用 SUBROUTINE
s 没有参数,连同共享数据,因此这些程序已经对实际参数进行了计算,可用通过 USE
语句。现在我需要重用其中的一些程序(考虑计算体积中的散度,一个 big DIMENSION(:,:,:)
数组,来自该体积中的矢量场,3 大 DIMENSION(:,:,:)
数组以派生类型粘合在一起),我想要
- 保留它们
SUBROUTINE
但删除USE
语句并使用IN
/OUT
/INOUT
虚拟参数(简单),或 - 在
FUNCTION
秒内转换它们(稍微难一点,因为我要学习一点)
我想这两种方法的性能可能存在差异,我想了解这一点。在下面的 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 之间做出选择实际上取决于它在您的实际应用程序中的清晰程度。只有一个组件的派生类型似乎是不现实的,除非您希望拥有这些组件的数组。