Как узнать, находится ли внутри параллельного региона openMP?

В своем коде я хочу избежать генерации исключений из любого параллельного региона openMP (поскольку это приводит к необработанному исключению, если оно не перехвачено в том же регионе). С этой целью я попытался использовать функцию библиотеки времени выполнения openmp.

omp_in_parallel();

чтобы решить, генерировать ли исключение или написать сообщение об ошибке и завершить работу. Однако в gcc 4.7.0 это не будет работать, если в параллельном регионе есть только один поток:

#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
  }
}

не приводит к ошибке: '3', а к завершению, вызванному после создания экземпляра 'int' Abort.

Это правильное поведение (omp_in_parallel())? (стандарт openMP выглядит достаточно расплывчатым) или ошибка в gcc? Как я могу исправить приведенный выше код для do_something(), чтобы он выдавал только не в параллельном регионе?


person Walter    schedule 22.10.2012    source источник


Ответы (1)


В стандарте OpenMP указано, что omp_in_parallel() возвращает true тогда и только тогда, когда окружающая область parallel активна. Активный регион parallel определяется как тот, который выполняется командой, состоящей из более чем одного потока. В вашем случае у вас есть неактивная параллельная область, так как есть только один поток. Таким образом, omp_in_parallel() возвращает false и выполняется throw. Это ошибка, потому что стандарт OpenMP ограничивает исключения одним и тем же параллельным регионом и потоком:

throw, выполняемый внутри области parallel, должен вызывать возобновление выполнения в той же области parallel, и тот же поток, который выдал исключение, должен его перехватить.

Это делает ваш код несовместимым, поскольку вы позволяете исключению проходить необработанным через параллельную область. Такая же ошибка возникает во время выполнения Intel OpenMP, поэтому это поведение не является специфичным для GCC.

На самом деле происходит то, что GCC преобразует регион OpenMP, заключая ваш код в блок try/catch с фильтром для всех исключений:

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

Именно этот всеобъемлющий фильтр исключений отвечает за сообщение о завершении.

Чтобы решить эту проблему, весь блок try/catch должен находиться внутри параллельной области, а не наоборот:

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

(дополнительный блок был добавлен, чтобы сделать компилятор Intel C++ счастливым; он не является строго необходимым для GCC)

Это выводит error: '3', как и ожидалось.

Изменить: Самое смешное, что ваш обработчик исключений даже не попадает в окончательный двоичный файл. Даже при заданном уровне оптимизации GCC по умолчанию (то есть при компиляции с g++ -fopenmp -o prog prog.cc) устранитель избыточности может определить, что ваш обработчик исключений никогда не будет достигнут из-за неявного внутреннего обработчика исключений, и поэтому ваш обработчик будет удален. Затем компилятор обнаруживает, что неявный обработчик завершения также является избыточным, так как уже есть обработчик исключения верхнего уровня для всего процесса, который делает то же самое (вызывает terminate()) и таким образом удаляет даже неявный. Окончательный код такой скудный и подлый — вообще никаких обработчиков исключений:

;; 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

}

Можно полюбить вариант -fdump-tree-all GCC.

Редактировать: Что касается вопроса о том, как исправить do_something() - используйте omp_get_level() вместо omp_in_parallel():

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

omp_get_level() возвращает уровень вложенности parallel областей, которые окружают вызов, независимо от того, активны они или нет.

person Hristo Iliev    schedule 22.10.2012
comment
+1 Никогда не пользовался OpenMP с исключениями и не знал -fdump-tree-all! - person Massimiliano; 22.10.2012
comment
Таким образом, ответ на вопрос как я могу исправить do_something() по существу: вы не можете. Невозможно выяснить, вызывается ли функция из параллельного региона openMP или нет, хотя информация где-то есть (но недоступна). Правильный? - person Walter; 23.10.2012
comment
@Walter, извините, я не увидел вопроса :) Конечно, вы можете это исправить. Расширяю свой ответ прямо сейчас... - person Hristo Iliev; 23.10.2012