Search code examples
arraysfortranoperator-overloadingopenmpderived-types

How to use openmp reduction on a derived type array in fortran?


Since OpenMP 4.5, arrays can be reduced using pragmas. But, I faced a problem when I tried to use it on arrays of derived type because !$OMP DECLARE REDUCTION is not found for this type. I understand the problem. After many tries, I finally found a program that seems to work. This is the code :

The module (module.f90):

module my_mod
    implicit none
    
    type :: my_type_t
        integer :: x
        integer :: y
    end type my_type_t
    
    interface operator(+)
    module procedure add_my_type_t_vec
    end interface
    
!$OMP DECLARE REDUCTION (+ : my_type_t : omp_out = omp_out + omp_in) INITIALIZER (omp_priv = omp_orig)
    
contains
    
    function add_my_type_t_vec(a,b) result(res)
        type(my_type_t), dimension(:),intent(in) :: a, b
        type(my_type_t), dimension(size(a)) :: res
        
        res(:)%x = a(:)%x + b(:)%x
        res(:)%y = a(:)%y + b(:)%y
    end function add_my_type_t_vec
    
end module my_mod

And the program (pgm.f90) that use it :

program parallel
    use omp_lib
    use my_mod
    implicit none
    
    type(my_type_t), dimension(:), allocatable :: private_var, shared_var
    integer :: i
    
    allocate(private_var(5), shared_var(5))
    shared_var%x=0
    shared_var%y=0
    

!$OMP PARALLEL NUM_THREADS(4) PRIVATE(private_var,i) REDUCTION (+ : shared_var) 

    !$OMP DO
    
    do i=1,1000
        private_var%x = 1
        private_var%y = 2
        shared_var = shared_var + private_var
    end do
    
    !$OMP END DO

!$OMP END PARALLEL
    print *, "Total : ", shared_var

end program parallel

Compilation with : gfortran -fopenmp module.f90 pgm.f90

To find this solution, I used the following answer https://stackoverflow.com/a/61148205/7462275.

It works but I am not sure that I will not have undefined behaviour in certain circumstances. And I do not fully understand the syntax and the mechanism of the DECLARE REDUCTION and INITIALIZER pragmas. Indeed, I did a copy/paste of the answer mentioned above and there is not explanation in it. Thanks for advice and explanations.


Solution

  • The issue with INITIALIZER (omp_priv = omp_orig) is that it only works, if the reduction variable is 0 before entering the region with the reduction. The private instance of the reduction variable should be initialized to the neutral element of the reduction operation. For + the neutral element is 0. For min it would be "positive infinity" or whatever is the closest representation for the data type.

    In this specific case, we need an array of 0 elements that has the right size which we can achieve by adding an initializer function that copies the size of the array:

    module my_mod
        implicit none
        
        type :: my_type_t
            integer :: x
            integer :: y
        end type my_type_t
        
        interface operator(+)
        module procedure add_my_type_t_vec
        end interface
        
    !$OMP DECLARE REDUCTION (+ : my_type_t : omp_out = omp_out + omp_in) INITIALIZER (omp_priv = init_my_type_vec(omp_orig))
        
    contains
        
        function add_my_type_t_vec(a,b) result(res)
            type(my_type_t), dimension(:),intent(in) :: a, b
            type(my_type_t), dimension(size(a)) :: res
            
            res%x = a%x + b%x
            res%y = a%y + b%y
        end function add_my_type_t_vec
        
        function init_my_type_vec(vec_in) result(vec_out)
            type(my_type_t), dimension(:),intent(in) :: vec_in
            type(my_type_t), dimension(size(vec_in)) :: vec_out
            
            vec_out%x=0
            vec_out%y=0
        end function init_my_type_vec
        
    end module my_mod
    

    Now, modifying the application code to enter the reduction with a different value shows the difference:

    program parallel
        use omp_lib
        use my_mod
        implicit none
        
        type(my_type_t), dimension(:), allocatable :: private_var, shared_var
        integer :: i
        
        allocate(private_var(5), shared_var(5))
        shared_var = my_type_t(5,10)
        
    
    !$OMP PARALLEL NUM_THREADS(4) PRIVATE(private_var,i) REDUCTION (+ : shared_var) 
    
        !$OMP DO
        
        do i=1,1000
            private_var%x = 1
            private_var%y = 2
            shared_var = shared_var + private_var
        end do
        
        !$OMP END DO
    
    !$OMP END PARALLEL
        print *, "Total : ", shared_var
    
    end program parallel
    

    Prints with the original initializer:

     Total :         1025        2050        1025        2050        1025        2050        1025        2050        1025        2050
    

    and with the modified initializer:

     Total :         1005        2010        1005        2010        1005        2010        1005        2010        1005        2010
    

    For further reading on how to use reductions I suggest to look at the OpenMP Examples document (openmp.org/wp-content/uploads/openmp-examples-6.0.pdf) to find more udr C and Fortran examples.