Search code examples
parallel-processingfortranopenmp

Using the fortran save attribute in parallel regions


I have a parallel region in my fortran code that uses OpenMP and calls subroutines that use variables with the save attribute in their scope. This is causing a problem because they are shared between threads, so my question is if there is a way to make these variables private while still being saved between subroutine calls, or will I need to input and output them?

Thanks


Solution

  • You can do it using threadprivate - the code below shows a couple of slightly different approaches. But please note

    a) The values are only guaranteed to be preserved between parallel regions if the parallel regions use the same number of threads

    b) Please think carefully whether you really need save, save and parallel programming are very rarely good bed fellows. There are one or two good uses (see e.g. Fortran OpenMP with subroutines and functions), but if there is an alternative way to do what you want to do (e.g. passing through the argument list) this will almost certainly cause you less pain in the long run

    (For some reason using a proper list breaks the formatting of the code below - if someone knows how to fix that, thanks!)

    ian@eris:~/work/stack$ cat threadprivate.f90
    Program test
    
      Implicit None
    
      Call do_something
      Call do_something
      Call do_something
    
      Write( *, * )
    
      !$omp parallel
      Call do_something_else
      Call do_something_else
      Call do_something_else
      !$omp end parallel
    
    Contains
    
      Subroutine do_something
    
        Use omp_lib
    
        Implicit None
    
        Integer, Save :: calls = 0
    
        Integer, Save :: stuff
    
        Logical, Save :: first = .True.
        !$omp threadprivate( first, stuff )
    
        calls = calls + 1
    
        ! Shouldn't scope threadprivate variables - they are already private
        !$omp parallel default( none ) shared( calls )
        If( first ) Then
           first = .False.
           stuff = omp_get_thread_num()
        Else
           stuff = stuff + 1
        End If
        Write( *, '( 3( a, 1x, i2, 1x ) )' ) 'do something call ', calls, &
             'thread = ', omp_get_thread_num(), 'stuff = ', stuff
        !$omp end parallel
    
      End Subroutine do_something
    
      Subroutine do_something_else
    
        Use omp_lib
    
        Implicit None
    
        Integer, Save :: calls = 0
    
        Integer, Save :: stuff
    
        Logical, Save :: first = .True.
        !$omp threadprivate( first, stuff, calls )
    
        calls = calls + 1
    
        If( first ) Then
           first = .False.
           stuff = omp_get_thread_num()
        Else
           stuff = stuff + 1
        End If
        Write( *, '( 3( a, 1x, i2, 1x ) )' ) 'do something else call ', calls, &
             'thread = ', omp_get_thread_num(), 'stuff = ', stuff
    
      End Subroutine do_something_else
    
    End Program test
    ian@eris:~/work/stack$ gfortran -std=f2008 -Wall -Wextra -O -g -fcheck=all -pedantic -fopenmp threadprivate.f90 
    ian@eris:~/work/stack$ export OMP_NUM_THREADS=2
    ian@eris:~/work/stack$ ./a.out
    do something call   1 thread =   0 stuff =   0
    do something call   1 thread =   1 stuff =   1
    do something call   2 thread =   1 stuff =   2
    do something call   2 thread =   0 stuff =   1
    do something call   3 thread =   1 stuff =   3
    do something call   3 thread =   0 stuff =   2
    
    do something else call   1 thread =   1 stuff =   1
    do something else call   2 thread =   1 stuff =   2
    do something else call   3 thread =   1 stuff =   3
    do something else call   1 thread =   0 stuff =   0
    do something else call   2 thread =   0 stuff =   1
    do something else call   3 thread =   0 stuff =   2
    ian@eris:~/work/stack$ export OMP_NUM_THREADS=4
    ian@eris:~/work/stack$ ./a.out
    do something call   1 thread =   3 stuff =   3
    do something call   1 thread =   2 stuff =   2
    do something call   1 thread =   1 stuff =   1
    do something call   1 thread =   0 stuff =   0
    do something call   2 thread =   1 stuff =   2
    do something call   2 thread =   3 stuff =   4
    do something call   2 thread =   0 stuff =   1
    do something call   2 thread =   2 stuff =   3
    do something call   3 thread =   3 stuff =   5
    do something call   3 thread =   1 stuff =   3
    do something call   3 thread =   0 stuff =   2
    do something call   3 thread =   2 stuff =   4
    
    do something else call   1 thread =   3 stuff =   3
    do something else call   2 thread =   3 stuff =   4
    do something else call   3 thread =   3 stuff =   5
    do something else call   1 thread =   1 stuff =   1
    do something else call   2 thread =   1 stuff =   2
    do something else call   3 thread =   1 stuff =   3
    do something else call   1 thread =   0 stuff =   0
    do something else call   2 thread =   0 stuff =   1
    do something else call   3 thread =   0 stuff =   2
    do something else call   1 thread =   2 stuff =   2
    do something else call   2 thread =   2 stuff =   3
    do something else call   3 thread =   2 stuff =   4
    ian@eris:~/work/stack$