Questions on variable scope in parallel computing

For historical reasons, initializing a variable has an implicit save behavior:

real(8) :: d = 0.0d0

is equal to

real(8), save :: d = 0.0d0

This is different from a separate declaration and assignment, i.e.

real(8) :: d
d = 0.0d0

The purpose of the save attribute is to save the value between calls of the function, implying the variables is placed in static storage and are persistent for the duration of the program. Another way I personally think of it is like a global variable, but limited to the scope of the function, if that makes sense. Or you could imagine it like a “function with memory”; the value of d is remembered across procedure calls.

Now in a multi-threaded program, each thread calling brent_pow will be referring to the same save’d variables thereby “stepping on each others toes”, i.e. modifying the values that another thread is still using. This is known as a data race. You could solve this issue by putting the function call in a single construct:

    !$omp parallel do collapse(2) private(inda, indb, aval, bval, xout, yout)
    do inda = 1, anum
        do indb = 1, bnum
            aval = agrid(inda)
            bval = bgrid(indb)
            !$omp single
            call fmintest(aval, bval, xout, yout)
            !$omp end single
            savemat(indb, inda, :) = [ xout, yout ]
        end do
    end do

but this would force the execution to become sequential (the threads would enter the function exclusively, i.e. one-by-one). Instead, by removing the saved variables, each thread keeps their own copy of d and the data-race is resolved.

The implicit save is a very common “gotcha” in Fortran. You can find other explanations at the following links:

I’ve also shown some examples related to save here:

3 Likes