How to pass an allocatable array to a subroutine?

How to pass an allocatable array to a subroutine?

I decided to create a new topic for my question that was answered in the discussion within another topic. My question was how to pass an allocatable array to a subroutine in general situations when the subroutine that the array was passed to passes it to another subroutine.

I'd like to thank jimdempseyatthecove for his short and self-explaining example how to do that. If I understand well, the correct way to pass an allocatable array is to define an explicit interface for the function that the arrays is passed to and to use the interface in the passing subroutine.

11 posts / 0 new
Last post
For more complete information about compiler optimizations, see our Optimization Notice.

Well, no, that's not really correct.

That an array is allocatable does not, in itself, change how you pass it to a routine. If you had an F77-style program where you changed one array to be allocatable, and allocated it, you would not have to change any other code in the program.

There are two particular situations related to allocatable arrays which would require an explicit interface.

  1. The dummy argument in the called routine is also declared allocatable. The only reason to do this is if you want to change the allocation status in the called routine.
  2. The dummy argument is a deferred-shape array with (:) bounds. Then, like any other situation where the dummy argument is deferred-shape, you need an explicit interface.

A nice feature of allocatable arrays, as compared to pointer arrays, is that the compiler knows a whole array reference is contiguous. So you can pass it like any other array and not incur copy overhead. But otherwise you can treat them as "normal" arrays in your program with a minimum of fuss.

Steve - Intel Developer Support

A different way, without interface

real function YourFunction(i,j,A,ni,nj)
integer :: i,j,ni,nj
real :: A(ni,nj)
... (additional data and code)
end  function YourFunction

The problem you had in your description of the example was your nx and ny were located in COMMON. You can code it as follows too:

program fnijx
    implicit none
    common/dims/nx,ny
    integer :: nx, ny
    ! Variables
    real, allocatable :: A(:,:)
    real fn, B
    ! Body of fnijx
    nx = 100
    ny = 100
    allocate(A(nx, ny))
    A = 0.0
    B = fn(12,34,A)
    print *, 'B =', B, A(12,34)
end program fnijx
    
real function fn(i,j, X)
    implicit none
    common/dims/nx,ny
    integer :: nx, ny
    integer :: i,j
    real :: X(nx, ny)
    X(i,j) = 123.456
    fn = X(i,j)
end function fn

However, doing so precludes compiler detection of improperly dimensioned array argument X to fn.

Jim Dempsey

www.quickthreadprogramming.com

Jim, the second example you've shown is exactly what I am using now. I have a file with COMMON /dims/ nx, ny, nz declared as integers and what I have then is that I include the file in every subroutine and function that uses any of the allocatable arrays that are allocated in the main program. I posted my first comment to start a discussion whether this approach is good; I am especially interested in my application's performance and if it is affected by the way I am passing arrays. I am typically passing tens of 3D allocatable arrays this way, I sometimes need to pass a different number of them to the same subroutine because some arrays are not need and are not thus allocated.

Bar one major exception, I think it would be a pretty special case that the method of passing an array made an appreciable difference to application runtime with all the serious optimisation options enabled, particularly if the arrays are not small.

(The one major exception I can think of is if the compiler needs to make a temporary copy of the array elements of the actual argument to make them contiguous.  That tends to favour assumed shape dummy arguments, but that may not be relevant here so lets put it aside for now.)

Consequently I'd be more worried about the clarity of your source, with respect to these arrays - will you or someone unfamiliar with the code understand what's going on in the future and how robust is your code in the face of programmer mistakes (noting that when the future is more than a few weeks distant I often find those categories of source code reader converge)?

I accept that personal coding style will influence this decision.  Having a consistent coding style also helps greatly from the point of view of others understanding what's going on.  They identify your style, they see certain patterns in the code and recognise what's going on, etc.

But style aside, one aspect that I find inconsistent about the approach of using common to transmit array dimensions is that you now have two channels to transfer information from the call site to the called procedure - array element data goes via the procedure passing "channel", array size data goes via the common "channel".  For someone looking at the call site and the called procedure and trying to understand how data flows, their job just got more complicated.

(Use of common specifically brings with it a heap of issues related to the underlying sequence association concept that invariably the programmer doesn't actually need to deal with for this sort of use case.  Making nx, ny, ... module variables solves that aspect - but even so, you still have two channels of information flow.)

In Fortran 77 to reduce the number of channels of information flow you'd pass the array dimensions as additional arguments.  You still need to consider multiple arguments when working out the array characteristics, but at least those arguments will be transparently listed at the point of call. 

Fortran 90 addressed this further with the use of assumed shape arrays - the array shape is also automatically "passed" across with the array data.  From the point of view of a source code reader all aspects of the array can now be presented in quite a compact manner.  Assumed shape brings with it a requirement for an explicit interface (ideally because the procedure is in a module), but from a code robustness point of view that should be a mandatory aspect of your programming style anyway.

If nx, ny, ... etc are program global things that are set once and forgot then perhaps the existance of two channels is not such an issue.  The source code reader still needs to identify the point at which they are originally set (which can be rather remote from the point of call) but they probably only have to do it once.  But, (remembering that programs have a tendancy to evolve) - if there's a reasonable chance that's not the case (what if a future variant of the program needs to consider having multiple copies of the array floating around at the one time?) using common becomes messy rather quickly.  You don't want to be seeing:

  COMMON/dims/nx,ny
  
  nx = nx1
  ny = ny1
  CALL proc(array1)
  
  nx = nx2
  ny = ny2
  CALL proc(array2)

in your code in a few years time.

Consequently for me, when it comes to passing arrays, assumed shape is what I use, unless there are specific reasons to the contrary.

One aspect of declaring an array in a subroutine argument list as allocatable, is
Can this array be allocated in the subroutine, with the address returned ?
I have never tried to do this as I only pass allocated arrays as subroutine arguments. ( and not declare them as ALLOCATABLE )
I have always assumed this to be illegal, but has 2003 or 2008 enabled this functionality ?
Otherwise, why would you declare the argument list array as allocatable ?

John

 

(For clarity - I think the OP is also talking about your current use case - an allocatable actual argument being passed to a "normal" dummy argument.  If not, my previous ramble won't make much sense.)

As long as the dummy argument is not intent(in) - yes.  Explicit interface required.  Introduced after F95 by a technical report (the same one that brought in allocatable components in derived types) that is widely implemented, included in F2003.  Can be Very Handy Indeed.  Consult your favourite source of Fortran 2003 documentation for more information.

John, Fortran 2003 added the ability to declare an allocatable dummy argument, function return value or derived type component. As you say, the only reason to declare a dummy argument to be allocatable is if you're going to change its allocation status in the routine. If you're just accessing the data, you don't need to do that.

Steve - Intel Developer Support

jirina,

I suggest you read IanH's excellent response a few times to appreciate his comments.

Assumed Shape (dummy arg has for example real :: A(:,:,:) and requires interface) results in a single channel identification for the data placement (paraphrase for IanH's words). The benefit being subroutine or function is more robust (I do not like this descriptive word in this context) by the fact that the subroutine/function is immune to shape changes which may occur when you or someone else makes a future change.

This said, assumed shape generally has higher overhead in array access, because the array descriptor tends to get referenced more frequently. In many compact routines the array descriptor may only be referenced once and in these cases the overhead is negligable. It would be easy enough to code both ways to run a test. In some cases, assumed shape can be faster (in those cases where an array temporary must be otherwise created)

real function fn(i,j, X)  
    implicit none
!DEC$ IF DEFINED(USE_ASSUMED_SHAPE)
    integer :: i,j  
    real :: X(:, :)  
!DEC$ ELSE
    common/dims/nx,ny  
    integer :: nx, ny  
    integer :: i,j  
    real :: X(nx, ny)  
!DEC$ ENDIF
    X(i,j) = 123.456  
    fn = X(i,j)  
end function fn 

You fix up the interface and define or not define USE_ASSUMED_SHAPE accordingly.

Condition this code into representative (or actual) subroutine and/or functions such that you get a representative impact on your application. The results you get from the above fn is most likely not representative of the overhead for your application.

Should you elect to keep the common or module nx, ny, nz, then it would be strongly advisable to insert conditional compilation directives such that the Debug build asserts the shape is what the subroutines and functions expect them to be. These tests may have to be placed at the calls as opposed to inside the subroutine/function.

Jim Dempsey

www.quickthreadprogramming.com

I would like to thank all of you for your detailed explanation; I really appreciate your time and efforts. I will read through the posts and try something in my code. Thank you!

Some more information on allocatable array as dummy arguments:

ftp://ftp.nag.co.uk/sc22wg5/N1351-N1400/N1379.pdf

Leave a Comment

Please sign in to add a comment. Not a member? Join today