Fortran 2008:函数 return 值 return 是如何编辑的?
Fortran 2008: How are function return values returned?
在现代 Fortran 中是否有可能 return 来自函数的数组其性能等同于让子例程填充作为参数传递的数组?
考虑例如作为简单的例子
PROGRAM PRETURN
INTEGER :: C(5)
C = FUNC()
WRITE(*,*) C
CALL SUB(C)
WRITE(*,*) C
CONTAINS
FUNCTION FUNC() RESULT(X)
INTEGER :: X(5)
X = [1,2,3,4,5]
END FUNCTION FUNC
SUBROUTINE SUB(X)
INTEGER :: X(5)
X = [1,2,3,4,5]
END SUBROUTINE SUB
END PROGRAM PRETURN
此处 C = FUNC()
行将从函数 return 值中复制值,然后从堆栈中丢弃 returned 数组。子例程版本 CALL SUB(C)
将直接填充 C
,避免额外的处理步骤和与临时数组相关的内存使用——但在像 SUM(FUNC())
这样的表达式中使用是不可能的。
但是,如果编译器实现选择在堆上分配所有数组,则可以简单地通过更改 C
的底层指针来分配 return 值,从而使两者具有相同的性能版本。*
此类优化是由通用编译器进行的,还是有其他方法可以在不增加性能开销的情况下获得函数语义?
* 使用可分配数组会更明显,但这会 运行 成为编译器支持问题。默认情况下,Intel fortran 不会在分配不同大小的数组时(重新)分配数组,但可以通过使用 ALLOCATE(C, SOURCE=FUNC())
语句实现相同的效果。 Gfortran 同时在赋值时进行自动分配,但有一个错误会阻止 ALLOCATE
语句,其中形状从 SOURCE
参数派生,并且修复程序尚未包含在二进制版本中。
Fortran 标准没有提及用该语言实现几乎所有内容的实际机制。该语言的语义是函数结果在赋值开始之前被完全评估。如果将目的地作为输出传递,那么如果函数由于某种原因未完成,则可能会部分修改该变量。编译器可能能够进行足够的重叠分析以对此进行一定程度的优化。我很确定英特尔 Fortran 不会这样做 - 语义限制很重要。
您的示例是一个玩具程序 - 更有趣的问题是是否有生产应用程序在其中适用且值得进行此类优化。
我将评论英特尔 Fortran 将更改其分配给可分配数组的默认行为,以便从版本 17 开始,将按照标准指定进行自动重新分配。
不过我有时也会这样。当我停下来想一想时,我意识到当涉及到 Fortran 时,函数就这样很好,子例程也很好。
想象一分钟,功能就在那里,我们有以下功能:
function doThings(param) results(thing)
integer :: thing
integer, intent(in out) :: param
! Local variables
integer :: genialUpdatedValue, onOfThePreviousResult
! some other declarations
! serious computation to do things
! and compute genialUpdatedValue and onOfThePreviousResult
param = genialUpdatedValue
thing = onOfThePreviousResult
end function doThings
我们有以下调用:
! some variables first
integer, parameter :: N_THINGS = 50 ! just love 50
integer :: myThing, myParam
integer, dimension(N_THINGS) :: moreThings
!
! Reading initial param from somewhere
! myParam now has a value
!
myThing = doThings(myParam)
那肯定可以,下面的呢
!
! Reading initial param from somewhere
! myParam now has a value
!
moreThing = doThings(myParam)
结果是什么?要不要
integer :: i
do i = 1, N_THINGS
moreThings(i) = doThings(myParam)
end do
还是这个
integer :: i, thing
thing = doThings(myParam)
do i = 1, N_THINGS
moreThings(i) = thing
end do
请记住 myParam
会被函数更改。有人可能会争辩说这是一个简单的案例,但可以想象结果不是整数而是具有大型数组成员的用户定义类型。
如果你仔细想想,你肯定会发现一些类似的问题。当然,可以在这里或那里添加更多限制以允许该功能,最终,当我们有足够的需求时,将在必要的限制下实施。希望对您有所帮助。
在现代 Fortran 中是否有可能 return 来自函数的数组其性能等同于让子例程填充作为参数传递的数组?
考虑例如作为简单的例子
PROGRAM PRETURN
INTEGER :: C(5)
C = FUNC()
WRITE(*,*) C
CALL SUB(C)
WRITE(*,*) C
CONTAINS
FUNCTION FUNC() RESULT(X)
INTEGER :: X(5)
X = [1,2,3,4,5]
END FUNCTION FUNC
SUBROUTINE SUB(X)
INTEGER :: X(5)
X = [1,2,3,4,5]
END SUBROUTINE SUB
END PROGRAM PRETURN
此处 C = FUNC()
行将从函数 return 值中复制值,然后从堆栈中丢弃 returned 数组。子例程版本 CALL SUB(C)
将直接填充 C
,避免额外的处理步骤和与临时数组相关的内存使用——但在像 SUM(FUNC())
这样的表达式中使用是不可能的。
但是,如果编译器实现选择在堆上分配所有数组,则可以简单地通过更改 C
的底层指针来分配 return 值,从而使两者具有相同的性能版本。*
此类优化是由通用编译器进行的,还是有其他方法可以在不增加性能开销的情况下获得函数语义?
* 使用可分配数组会更明显,但这会 运行 成为编译器支持问题。默认情况下,Intel fortran 不会在分配不同大小的数组时(重新)分配数组,但可以通过使用 ALLOCATE(C, SOURCE=FUNC())
语句实现相同的效果。 Gfortran 同时在赋值时进行自动分配,但有一个错误会阻止 ALLOCATE
语句,其中形状从 SOURCE
参数派生,并且修复程序尚未包含在二进制版本中。
Fortran 标准没有提及用该语言实现几乎所有内容的实际机制。该语言的语义是函数结果在赋值开始之前被完全评估。如果将目的地作为输出传递,那么如果函数由于某种原因未完成,则可能会部分修改该变量。编译器可能能够进行足够的重叠分析以对此进行一定程度的优化。我很确定英特尔 Fortran 不会这样做 - 语义限制很重要。
您的示例是一个玩具程序 - 更有趣的问题是是否有生产应用程序在其中适用且值得进行此类优化。
我将评论英特尔 Fortran 将更改其分配给可分配数组的默认行为,以便从版本 17 开始,将按照标准指定进行自动重新分配。
不过我有时也会这样。当我停下来想一想时,我意识到当涉及到 Fortran 时,函数就这样很好,子例程也很好。
想象一分钟,功能就在那里,我们有以下功能:
function doThings(param) results(thing)
integer :: thing
integer, intent(in out) :: param
! Local variables
integer :: genialUpdatedValue, onOfThePreviousResult
! some other declarations
! serious computation to do things
! and compute genialUpdatedValue and onOfThePreviousResult
param = genialUpdatedValue
thing = onOfThePreviousResult
end function doThings
我们有以下调用:
! some variables first
integer, parameter :: N_THINGS = 50 ! just love 50
integer :: myThing, myParam
integer, dimension(N_THINGS) :: moreThings
!
! Reading initial param from somewhere
! myParam now has a value
!
myThing = doThings(myParam)
那肯定可以,下面的呢
!
! Reading initial param from somewhere
! myParam now has a value
!
moreThing = doThings(myParam)
结果是什么?要不要
integer :: i
do i = 1, N_THINGS
moreThings(i) = doThings(myParam)
end do
还是这个
integer :: i, thing
thing = doThings(myParam)
do i = 1, N_THINGS
moreThings(i) = thing
end do
请记住 myParam
会被函数更改。有人可能会争辩说这是一个简单的案例,但可以想象结果不是整数而是具有大型数组成员的用户定义类型。
如果你仔细想想,你肯定会发现一些类似的问题。当然,可以在这里或那里添加更多限制以允许该功能,最终,当我们有足够的需求时,将在必要的限制下实施。希望对您有所帮助。