7

在现代 Fortran 中是否可以从函数返回一个数组,其性能相当于让子例程填充作为参数传递的数组?

考虑例如作为简单的例子

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()将从函数返回值中复制值,然后从堆栈中丢弃返回的数组。子例程版本CALL SUB(C)将直接填充C,避免了与临时数组相关的额外应对步骤和内存使用——但在表达式中使用却是SUM(FUNC())不可能的。

但是,如果编译器实现选择在堆上分配所有数组,则可以简单地通过更改底层指针来分配返回值C,从而在两个版本之间实现相同的性能。*

这种优化是由常见的编译器进行的,还是有其他方法可以在没有性能开销的情况下获得函数语义?


* 使用可分配数组会更明显,但这会遇到编译器支持问题。默认情况下,英特尔 fortran 在分配不同大小的数组时不会(重新)分配数组,但通过使用ALLOCATE(C, SOURCE=FUNC())语句允许相同的效果。Gfortran 同时在分配时进行自动分配,但有一个错误会阻止ALLOCATE从参数派生形状的语句,SOURCE并且该修复尚未包含在二进制版本中。

4

2 回答 2

9

Fortran 标准对用该语言实现几乎所有内容的实际机制保持沉默。该语言的语义是在分配开始之前对函数结果进行完全评估。如果将目标作为输出传递,那么如果函数由于某种原因没有完成,则变量可能会被部分修改。编译器可能能够进行足够的重叠分析来对此进行一些优化。我很确定英特尔 Fortran 不会这样做 - 语义限制很重要。

您的示例是一个玩具程序 - 更有趣的问题是是否存在这样的优化适用且值得的生产应用程序。

我将评论英特尔 Fortran 将更改其分配给可分配数组的默认行为,以便从版本 17 开始,将按照标准的规定进行自动重新分配。

于 2016-06-21T18:09:59.017 回答
0

我有时也有同样的情况。当我停下来想一想时,我意识到在 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会被函数更改。有人可能会说这是一个简单的案例,但可以想象结果不是整数,而是具有大数组成员的用户定义类型。

如果你仔细想想,你肯定会发现一些类似的问题。当然,可以在这里和那里添加更多限制以允许该功能,最终,当我们有足够的需求时,它将在必要的限制下实施。我希望这会有所帮助。

于 2016-06-21T19:00:56.937 回答