Home > Net >  OpenMP reduction: min gives incorrect result
OpenMP reduction: min gives incorrect result

Time:09-17

I want to use an OpenMP reduction in a parallel region, outside a for loop. According to the OpenMP reference, reduction clauses can be used in parallel regions, so no for loop or sections should be necessary.

However, when using an OpenMP reduction (min:...) in a parallel region, I'm getting incorrect results. If I use exactly the same structure for a reduction (max:...) however, the result is correct. If I use a loop for the min reduction (#pragma omp for reduction (min:...)), the results are correct, but I don't think that should be necessary. Here's my code:

#include <omp.h>
#include <iostream>
#include <limits>
#include <algorithm>

int main(){
    auto minVar { std::numeric_limits<int>::max() };
    auto maxVar { std::numeric_limits<int>::min() };
    auto minVarLoop { std::numeric_limits<int>::max() };

    #pragma omp parallel
    {
        int threadNo { omp_get_thread_num() };

        #pragma omp reduction (min:minVar)
        minVar = std::min(minVar, threadNo);
        // minVar = minVar < threadNo ? minVar : threadNo; // also doesn't work
        
        #pragma omp for reduction(min:minVarLoop)
        for (int i=0; i<omp_get_num_threads();   i){
            minVarLoop = std::min(minVarLoop, threadNo);
        }
        
        #pragma omp reduction (max:maxVar)
        maxVar = std::max(maxVar, threadNo);
    }
    std::cout
        <<   "min thread num: " << minVar
        << "\nmax thread num: " << maxVar
        << "\nmin thread num from Loop: " << minVarLoop
        << "\n";

    return 0;
}

The expected output, with 16 threads, is

min thread num: 0
max thread num: 15
min thread num from Loop: 0

The output I get is

min thread num: 12 // or any other number between 0 and 15
max thread num: 15
min thread num from Loop: 0

I'm compiling using g version 10.3.0 on Ubuntu 21.04, using only the flag -fopenmp.

What am I overlooking?

EDIT: not initialising minVar, i.e. using int minVar; somehow makes it work, but I don't find that a satisfying solution. Also, attempting the same with maxVar, makes those results incorrect. Oh my.

CodePudding user response:

You're using a feature that is not yet supported by the compiler. If you compile your code with -Wall you will see that GCC 10.3.0 shows this warning:

red.cc:15: warning: ignoring ‘#pragma omp reduction’ [-Wunknown-pragmas]
   15 |         #pragma omp reduction (min:minVar)
      |
red.cc:24: warning: ignoring ‘#pragma omp reduction’ [-Wunknown-pragmas]
   24 |         #pragma omp reduction (max:maxVar)
      |

If you correct the code, like I'm showing below using the correct spelling (see scope Construct) the compiler will still object, as it does not support version 5.1 of the OpenMP API yet:

red.cc:15: warning: ignoring ‘#pragma omp scope’ [-Wunknown-pragmas]
   15 |         #pragma omp scope reduction (min:minVar)
      |
red.cc:24: warning: ignoring ‘#pragma omp scope’ [-Wunknown-pragmas]
   24 |         #pragma omp scope reduction (max:maxVar)
      |

The correct spelling would then look like this:

#include <omp.h>
#include <iostream>
#include <limits>
#include <algorithm>

int main(){
    auto minVar { std::numeric_limits<int>::max() };
    auto maxVar { std::numeric_limits<int>::min() };
    auto minVarLoop { std::numeric_limits<int>::max() };

    #pragma omp parallel
    {
        int threadNo { omp_get_thread_num() };

        #pragma omp scope reduction (min:minVar)
        minVar = std::min(minVar, threadNo);
        // minVar = minVar < threadNo ? minVar : threadNo; // also doesn't work
        
        #pragma omp for reduction(min:minVarLoop)
        for (int i=0; i<omp_get_num_threads();   i){
            minVarLoop = std::min(minVarLoop, threadNo);
        }
        
        #pragma omp scope reduction (max:maxVar)
        maxVar = std::max(maxVar, threadNo);
    }
    std::cout
        <<   "min thread num: " << minVar
        << "\nmax thread num: " << maxVar
        << "\nmin thread num from Loop: " << minVarLoop
        << "\n";

    return 0;
}

The varying results are due to a race condition. Don't ask why you're getting the right result for max, I do get different results any time, I run the code.

Another correct version (not needing bleeding edge support for version 5.1 of the OpenMP API) of your code would be this:

#include <omp.h>
#include <iostream>
#include <limits>
#include <algorithm>

int main(){
    auto minVar { std::numeric_limits<int>::max() };
    auto maxVar { std::numeric_limits<int>::min() };
    auto minVarLoop { std::numeric_limits<int>::max() };

    #pragma omp parallel reduction(min:minVar) reduction(max:maxVar)
    {
        int threadNo { omp_get_thread_num() };

        minVar = std::min(minVar, threadNo);

        #pragma omp for reduction(min:minVarLoop)
        for (int i=0; i<omp_get_num_threads();   i){
            minVarLoop = std::min(minVarLoop, threadNo);
        }

        maxVar = std::max(maxVar, threadNo);
    }
    std::cout
        <<   "min thread num: " << minVar
        << "\nmax thread num: " << maxVar
        << "\nmin thread num from Loop: " << minVarLoop
        << "\n";

    return 0;
}
  • Related