2

I wonder how "%" works on array of derived type.

I wrote the following program where the "+" operator has been overloaded :

module my_mod

  implicit none

  type :: my_type
    real :: r1 , r2
    integer :: i1, i2
  end type my_type
  
  interface operator(+)
    module procedure add_my_type_vec, add_my_type
  end interface
  
contains
  function add_my_type_vec(a,b) result(res)
    type(my_type), dimension(:),intent(in) :: a, b
    type(my_type), dimension(size(a)) :: res

      res%r1 = a%r1 + b%r1
      res%r2 = a%r2 + b%r2
      res%i1 = a%i1 + b%i1
      res%i2 = a%i2 + b%i2
      
  end function add_my_type_vec
  
  function add_my_type(a,b) result(res)
    type(my_type), intent(in) :: a, b
    type(my_type) :: res

      res%r1 = a%r1 + b%r1
      res%r2 = a%r2 + b%r2
      res%i1 = a%i1 + b%i1
      res%i2 = a%i2 + b%i2
      
  end function add_my_type
  
end module my_mod

program my_pgm
    use my_mod
    implicit none
    
    type(my_type),allocatable, dimension(:) :: my_vec1, my_vec2, my_vec3
    
    write(*,*) "Sum on type : ", my_type(1.0, 2.0, 1, 2) + my_type(1.0, 2.0, 1, 2)

    allocate(my_vec1(1000), my_vec2(1000), my_vec3(1000))
    my_vec1 = my_type(1.0, 2.0, 1, 2)
    my_vec2 = my_type(1.0, 2.0, 1, 2)
    my_vec3 = my_vec1 + my_vec2
    
    write(*,*) "Sum on vec of type : ", my_vec3(123)
    

end program my_pgm

In the add_my_type_vec function, how does % work? For example, in the instruction res%r1 = a%r1 + b%r1, is there the creation (i.e. memory copy) of two arrays of real that contain only a%r1 and b%r1, the sum is done on these arrays, and after the assignment is done on res%r1 ? I guess it's more complicated.

8
  • 3
    Are you interested in what must happen (for the compiler to follow the standard), what may happen (within the constraints of the standard), or what practically happens for a representative compiler? Commented Apr 19 at 11:54
  • 3
    Do you have specific concerns about how summation of arrays works in the case of those arrays being component, rather than any other appearance of arrays? Commented Apr 19 at 11:58
  • @francescalus. Thanks for your interest in my question. I would say everything interests me.It is just for curiosity., all my codes work and I found very useful to use % on array of derived types. But, for example, in term of memory optimisation, is it a good idea ? Commented Apr 19 at 12:01
  • @francescalus. For example, i found this discussion (stackoverflow.com/q/55697615/7462275)very interesting. Commented Apr 19 at 12:08
  • 1
    You have 2 answers that address your question. However, if you are using gfortran as your compiler, you can use the -Warray-temporaries option to generate warnings when temporary arrays are used. You can also use -fdump-tree-original to dump an intermediate representation of the parsed code. It's a C-like syntax. Commented Apr 19 at 16:09

2 Answers 2

4

Compilers are generally free to create array temporaries whenever they'd like.1 There are times, such as you'll see in may posts here where temporary copies are effectively required or are highly desirable. This other answer, for example, takes some cases.

As a practical example, this is a case where thinking can be elaborated, rather than a strict answer given.

In the whole code here, there are many opportunities for an array temporary to be created around add_my_type_vec, such as:

  • the array dummy arguments a and b could be copied
  • the arrays on the right-hand sides of the assignments (a%r1, b%r1, etc.) could each be copied
  • evaluation of the right-hand sides (a%r1+b%r1, etc.) could go in to a temporary location
  • the function result could be a temporarily stored variable which is later copied to the left-hand side of the assignment of its result.

Not one copy of any class is required here, or is particularly beneficial.

The dummy arguments a and b are assumed shape, and the compiler knows that they are (simply) contiguous.

The array a%r1 (etc.) is not contiguous, but is of constant stride: a compiler can easily handle striding through the elements.

The left-hand side of res%r1 = a%r1 + b%r1 does not appear in any way (especially not a complicated way) on the right-hand side: evaluation of the right-hand side does not affect the left-hand side so no copy is required to hold the evaluation before assignment.

Equally, with the assignment of the function result, there's no interactions between any of the variables involved.

There's no need to work around the demands of finalization anywhere.

Essentially, all the compiler has to do is iterate over the elements of my_vec3 and directly assign the pair-wise sum of elements of my_vec1 and my_vec2, striding through those arrays in pretty boring ways.

A compiler could decide to do any of the copies if it thought wise, or if it wanted to play extra safe. Usually, you can ask a compiler to tell you (compile- or run-time) when it's making a copy or is putting the machinery in place to enable a copying decision.

The only reason to be worried about the components (use of %) is that the referenced arrays are not contiguous. Fortran is very well able to cope with non-contiguous arrays without requiring copies. Restrictions on "complicated" arrays and "complicated" uses of components are there to make handling these common cases truly very boring (for compiler writers).


1 A compiler is required to ensure that effects on a temporary are effects on an entity associated with that temporary. For example, if there's a temporary copy of a dummy argument then the actual argument must reflect any (appropriate) changes. Fortran's rules on aliasing are restrictions on programs which make the compiler's life much easier in determining when effects are required to be visible in techniques such as copy-in/copy-out.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for answer and explanation on "strided" arrays. It is very clear and I could test it with compiler options given by Steve in its comment.
3

In in array operation res(:)%r1 = a(:)%r1 + b(:)%r1 such as the ones in add_my_type_vec(), I expect any decent optimizing compiler to NOT create temporary arrays.

Still, this approach is probably not optimum in terms of memory access: a(:)%r1 is a non-contiguous array with a stride of 4 (because you have 4 numeric storage units in your derived type), and AFAIK vector instructions are less efficient on strided arrays. Moreover, by sequentially performing the 4 array operations, only 25% of the content of a cache line is used before the cache line is wiped out, and reloaded later on.

On the other hand, by summing element by element (of the derived type array), the caches are better used, but there is no way to take advantage of the vector instructions.

At the end, only a benchmark can tell you what is the most efficient approach... for the given machine/compiler combination you are using.

Note that you don't really need to write 2 separate routines here. You can write a single elemental routine that will handle both the scalar and array additions (what the compiler will do behind the scene is not not obvious, though)

module my_mod

  implicit none

  type :: my_type
    real :: r1 , r2
    integer :: i1, i2
  end type my_type
  
  interface operator(+)
    module procedure add_my_type
  end interface
  
contains
  
  elemental function add_my_type(a,b) result(res)
    type(my_type), intent(in) :: a, b
    type(my_type) :: res

      res%r1 = a%r1 + b%r1
      res%r2 = a%r2 + b%r2
      res%i1 = a%i1 + b%i1
      res%i2 = a%i2 + b%i2
      
  end function add_my_type
  
end module my_mod

program my_pgm
    use my_mod
    implicit none
    
    type(my_type),allocatable, dimension(:) :: my_vec1, my_vec2, my_vec3
    
    write(*,*) "Sum on type : ", my_type(1.0, 2.0, 1, 2) + my_type(1.0, 2.0, 1, 2)

    allocate(my_vec1(1000), my_vec2(1000), my_vec3(1000))
    my_vec1 = my_type(1.0, 2.0, 1, 2)
    my_vec2 = my_type(1.0, 2.0, 1, 2)
    my_vec3 = my_vec1 + my_vec2
    
    write(*,*) "Sum on vec of type : ", my_vec3(123)
    

end program my_pgm

1 Comment

Thanks for answer and the elemental routine.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.