Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to find out if inside an openMP parallel region?

Tags:

c++

c

openmp

In my code, I want to avoid throwing exceptions from inside any openMP parallel region (because this results in an unhandled exception if not caught within the same region). To this end, I tried to use the openmp run-time library function

omp_in_parallel();

to decide whether to throw an exception or write out an error message and terminate. However, under gcc 4.7.0 this will not work if there is only one thread to a parallel region:

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

void do_something()
{
  if(!omp_in_parallel())           // omp_in_parallel() returns false!
    throw 3;                       // so should be able to safely throw
}

int main()
{
  omp_set_num_threads(1);
  try {
#   pragma omp parallel
    do_something();
  } catch(int e) {
    std::cerr<<"error: '"<<e<<"'\n";  // never gets here
  }
}

does not result in error: '3' but in terminate called after throwing an instance of 'int' Abort.

Is this the correct behaviour (of omp_in_parallel())? (the openMP standard appears sufficiently vague) or a bug in gcc? How can I fix the above code for do_something() so that it throws only when not in a parallel region?

like image 296
Walter Avatar asked Oct 22 '12 12:10

Walter


1 Answers

The OpenMP standard states that omp_in_parallel() returns true if and only if the enclosing parallel region is active. An active parallel region is defined to be one that is executed by a team consisting of more than one thread. In your case you have an inactive parallel region since there is one thread only. Thus omp_in_parallel() returns false and the throw is executed. It errs because the OpenMP standard restricts exceptions to the same parallel region and thread:

A throw executed inside a parallel region must cause execution to resume within the same parallel region, and the same thread that threw the exception must catch it.

This makes your code non-conformant since you let an exception pass unhandled through the parallel region. The same error is emitted by the Intel OpenMP run-time so it is not a GCC-specific behaviour.

What happens actually is that GCC transforms the OpenMP region by wrapping your code in a try/catch block with a catch-all exception filter:

#pragma omp parallel [child fn: main.omp_fn.0 (???)]
  {
    try
      {
        do_something ();
      }
    catch
      {
        <<<eh_filter (NULL)>>>
          {
            terminate ();
          }
      }
    #pragma omp return
  }

It is this catch-all exception filter that is responsibe for the termination message.

To resolve this the entire try/catch block should be inside the parallel region and not vice versa:

# pragma omp parallel
{
   try {
      do_something();
   } catch(int e) {
      std::cerr<<"error: '"<<e<<"'\n";  // never gets here
   }
}

(the additional block was added to make Intel C++ Compiler happy; it is not strictly necessary for GCC)

This outputs error: '3' as expected.

Edit: The funny thing is that your exception handler doesn't even make it into the final binary. Even given the default optimisation level of GCC (i.e. compiling with g++ -fopenmp -o prog prog.cc), the redundancy eliminator is able to detect that your exception handler would never be reached because of the implicit inner exception handler and thus your handler is removed. Then the compiler finds out that the implicit termination handler is also redundant, since there is already a top-level exception handler for the whole process that does the same (calls terminate()) and thus removes even the implicit one. The final code is so lean and mean - no exception handlers at all:

;; Function int main() (main, funcdef_no=970, decl_uid=20816, cgraph_uid=212)

int main() ()
{
  int e;
  int D.20855;
  struct basic_ostream & D.20854;
  struct basic_ostream & D.20853;
  void * D.20852;
  register int * D.20819;

<bb 2>:
  omp_set_num_threads (1);
  __builtin_GOMP_parallel_start (main._omp_fn.0, 0B, 0);
  main._omp_fn.0 (0B);
  __builtin_GOMP_parallel_end ();
  D.20855_1 = 0;
  // <------ See, ma', no exception handling at all :)

<L0>:
  return D.20855_1;

}

;; Function <built-in> (main._omp_fn.0, funcdef_no=976, decl_uid=20857, cgraph_uid=222)

<built-in> (void * .omp_data_i)
{
<bb 2>:
  do_something ();
  return;
  // <------ See, ma', they've nuked the implicit termination handler

}

One gets to love the -fdump-tree-all GCC option.

Edit: As to the question on how to fix do_something() - use omp_get_level() instead of omp_in_parallel():

void do_something()
{
   if(omp_get_level() == 0)
     throw 3;
}

omp_get_level() returns the level of nesting of the parallel regions that enclose the call, no matter if they are active or not.

like image 128
Hristo Iliev Avatar answered Oct 02 '22 21:10

Hristo Iliev