Fortran 2008: How are function return values returned?
Asked Answered
F

2

8

Is it possible in modern Fortran to return an array from a function with performance equivalent to having a subroutine fill an array passed as argument?

Consider e.g. as simple example

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

Here the line C = FUNC() would copy the values from the function return value, before discarding the returned array from the stack. The subroutine version CALL SUB(C) would fill C directly, avoiding the extra coping step and memory usage associated with the temporary array – but making usage in expresions like SUM(FUNC()) impossible.

If however, the compiler implementation chose to allocate all arrays on the heap, the return value could be assigned simply by changing the underlying pointer of C, resulting in equivalent performance between the two versions.*

Are such optimizations made by common compilers, or is there some other way to get function semantics without the performance overhead?


* It would be more apparent with allocatable arrays, but this would run into compiler-support issues. Intel fortran by default doesn't (re)allocate arrays upon assignment of a different-size array but allows the same effect by using an ALLOCATE(C, SOURCE=FUNC()) statement. Gfortran meanwhile does the automatic allocation on assignment but has a bug that prevents ALLOCATE statements where the shape is derived from the SOURCE argument and the fix hasn't been included in binary releases yet.

Fondly answered 21/6, 2016 at 14:22 Comment(0)
N
11

The Fortran standard is silent on the actual mechanism for implementing pretty much anything in the language. The semantics of the language are that the function result is completely evaluated before the assignment starts. If one passed the destination as the output then if the function didn't complete for some reason the variable might be partially modified. A compiler might be able to do enough overlap analysis to optimize this somewhat. I am pretty sure that Intel Fortran doesn't do this - the semantic restrictions are significant.

Your example is a toy program - the more interesting question is if there are production applications where such an optimization would be applicable and worthwhile.

I will comment that Intel Fortran will change its default behavior for assignments to allocatable arrays so that, as of version 17, the automatic reallocation will occur as specified by the standard.

Nork answered 21/6, 2016 at 18:9 Comment(2)
By any chance, are you aware of attempts to establish "move assignment" semantics for fortran? While you made good points against doing so by default, it seems like something that could be implemented as an explicit attribute to the function / return value.Fondly
I have never heard this discussed among the standards committee (have been a member since 2008). Since you can accomplish what you want with a subroutine call, it seems an unnecessary complication to me.Nork
I
1

I sometime have the same though. When I stop and think about it a moment, I realize that functions are good the way they are and subroutines are good the way they are too when it comes to fortran.

Imagine a minute that the capability is there and we have the following function:

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

And we have the following calls:

! 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)

That is definitely OK, what about the following

!
! Reading initial param from somewhere
! myParam now has a value
!
moreThing = doThings(myParam)

What is that going to give as result? Shall it be

integer :: i
do i = 1, N_THINGS
    moreThings(i) = doThings(myParam)
end do

or shall it be this one

integer :: i, thing
thing = doThings(myParam)
do i = 1, N_THINGS
    moreThings(i) = thing
end do

Remember that myParam gets changed by the function. One can argue that this is a simple case, but image that the result was not an integer but a user defined type with large array members.

If you think about it, you will definitely find some problems like those. Of course more restriction can be added here and there to allow that feature, and eventually, when we have enough demand, it will be implemented with the necessary restrictions. I hope that helps.

Ideology answered 21/6, 2016 at 19:0 Comment(4)
Could you please clarify what you consider the issue here? The code is somewhat confusing, but I suppose the issue is about returning a variable which has the SAVE attribute, thus making returning a pointer to the data and returning the value by copying differ.Fondly
We do not have the save attribute here. The problem is that the return value is computed based on param and param is being changed all the time. So calling once and using the same value to set all the cells of the array is totally different from calling the function for each cell of the array. Because any time you call, the return value is different because of param being changed in the function. The simplest illustration would be that the function increment param and the return value is the square of param.Ideology
So essentially you're not returning an array at all? I missed that part. This would make the answer entirely unrelated to the question as far as I can tell. While my example was intentionally a simple toy example – maybe over-simplified because I didn't even use allocatables – it is specifically about efficiently returning the result of array-valued functions by avoiding copying the array from one memory region to another.Fondly
@Fondly You spot it right! It is not returning array, and that was exactly the point because fortran allows the assignment where the left side is an array and the right side is a scalar. Also, I understood perfectly the efficiency problem because I have been thinking about the exact same feature especially for assignment overloading. I wanted to point out some problems that programmers will face with such a feature. It is a great feature for efficiency. We just have to think about the eventual problems and how to overcome them. The efficiency could win and the feature is eventually implemented.Ideology

© 2022 - 2024 — McMap. All rights reserved.