5

私のコードでは、openMP 並列領域内から例外をスローすることは避けたいと考えています (同じ領域内でキャッチされない場合、未処理の例外が発生するため)。このために、openmp ランタイム ライブラリ関数を使用しようとしました。

omp_in_parallel();

例外をスローするか、エラー メッセージを出力して終了するかを決定します。ただし、gcc 4.7.0 では、並列領域へのスレッドが 1 つしかない場合、これは機能しません。

#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()上記のコードを修正して、並列領域にない場合にのみスローするようにするにはどうすればよいですか?

4

1 に答える 1

9

OpenMP標準では、囲んでいる領域がアクティブである場合にのみtrueomp_in_parallel()を返すと規定されています。アクティブ領域は、複数のスレッドで構成されるチームによって実行される領域として定義されます。あなたの場合、スレッドが1つしかないため、非アクティブな並列領域があります。したがって、が返され、が実行されます。OpenMP標準が例外を同じ並列領域とスレッドに制限しているため、エラーが発生します。parallelparallelomp_in_parallel()falsethrow

throwリージョン内で実行されるとparallel、同じリージョン内で実行が再開されparallel、例外をスローした同じスレッドがそれをキャッチする必要があります。

これにより、例外が並列領域を未処理のまま通過するため、コードが不適合になります。同じエラーがIntelOpenMPランタイムによって発行されるため、GCC固有の動作ではありません。

実際に起こることは、GCCがtry/catchキャッチオール例外フィルターを使用してコードをブロックにラップすることによってOpenMP領域を変換することです。

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

(インテル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-allGCCオプションを気に入るはずです。

編集:修正方法に関する質問については、次の代わりにdo_something()使用してください:omp_get_level()omp_in_parallel()

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

omp_get_level()parallelアクティブかどうかに関係なく、コールを囲むリージョンのネストのレベルを返します。

于 2012-10-22T13:57:32.877 に答える