2

Fortran 77 ルーチンを呼び出す C++ プログラムを 1 つ開発しています。メインの C++ プログラムはマルチスレッドで実行できます。ただし、Fortran 77 ルーチンは、引数に応じて各呼び出しで変更されるいくつかの共通ブロックを非表示にすることがあります。

すべての共通ブロックが複数のスレッド間で共有される可能性があり、これらのブロックへの同時アクセスがおそらくすべてを混乱させるのではないかと心配しています。

  • 最初の質問: 私は正しいですか? 共通ブロックは複数のスレッド間で共有されますか?

  • 2 番目の質問: それを回避する簡単な方法はありますか? Fortran ルーチンを書き直すのは手が届かないように思えます。むしろ、各スレッドがすべての共通ブロックの独自のコピーを持つようにする方法を探しています (サイズが大きくなく、コピーが高速である必要があります)。コンパイル オプションが役立つかどうか、または OpenMP が役立つかどうかはわかりません。

4

4 に答える 4

2

はい、共通ブロックは共有されています。

OpenMP では、共通ブロックを THREADPRIVATE として指定できます。各スレッドは、共通ブロックの新しいインスタンスを動的に作成します。元のデータからデータをコピーするには、COPYIN 指定子を使用します。OpenMP スレッドプライベートとプライベートの違いも参照してください

基本的な構文は次のとおりです。

!$OMP THREADPRIVATE (/cb/, ...)  

ここで、cb は共通ブロックの名前です。https://computing.llnl.gov/tutorials/openMP/#THREADPRIVATEを参照

于 2015-08-25T20:31:09.240 に答える
2

共通ブロックがスレッドセーフではないことは正しいです。これらはグローバル データであり、すべて同じストレージ アソシエーションを共有する任意の有効範囲単位で変数を宣言できます。C++ でグローバル変数に書き込み、すべてのスレッド同期の問題が発生する場合、結果は本質的に同じです。

残念ながら、それを回避する簡単な方法はないと思います。マルチスレッドのアプローチを維持する必要がある場合、過去に投げかけられたアイデアの 1 つは、すべての変数を共通ブロックからユーザー定義型に移動し、その型のインスタンスをアクセスが必要なプロシージャに渡すことです。それらに (スレッドごとに 1 つのインスタンス)。ただし、これには、実装するコードへの潜在的に高価な変更が含まれます。

また、Fortran コードの他のスレッド セーフの問題も確認する必要があります (これは完全なリストではありません)。

  • IO ユニットはスレッドごとに一意である必要があります。そうしないと、ファイルの入出力が信頼できなくなります
  • 属性を持つ変数SAVE(モジュール変数内および宣言時に初期化される変数内で暗黙的) には問題があります (これらの変数はプロシージャ呼び出し間で永続的です)。この属性の暗黙性もコンパイラ/標準に依存するため、これはさらに大きな問題になる可能性があります。
  • 属性を持つプロシージャを宣言しますRECURSIVE。これは、関数が再入可能であることを意味します。これは、コードを変更するのではなく、コンパイラの openmp オプションを使用してコンパイルすることによっても満たすことができます。

検討できるもう 1 つの方法は、マルチスレッドではなく、マルチプロセッシングまたはメッセージ パッシングを使用してコードを並列化することです。これにより、Fortran コードのスレッド セーフの問題は回避されますが、コストがかかる可能性のある別のコード アーキテクチャの変更が発生します。

以下も参照してください。

于 2015-08-25T16:54:34.460 に答える
0

あなたの答え、特にOpenMPに関するヒントをありがとう、それは確かに実行可能です. 完全に確実にするために、小さなプログラムを作成しました。これは、1 つのメイン C++ プログラムで呼び出される 1 つの Fortran 77 部分で構成されています (これは私の関心事です)。

fortran 77 ルーチンfunc.f :

  subroutine set(ii, jj)
  implicit none

  include "func.inc"
  integer ii, jj
  integer OMP_GET_NUM_THREADS, OMP_GET_THREAD_NUM

  i = ii + 1
  j = jj

  !$OMP CRITICAL
  print *, OMP_GET_THREAD_NUM(), OMP_GET_NUM_THREADS(), i, j
  !$OMP END CRITICAL
  return
  end


  subroutine func(n, v)
  implicit none

  include "func.inc"

  integer n, k
  integer v(n)

  do k = i, j
     a = k + 1
     b = a * a
     c = k - 1
     v(k) = b - c * c
  enddo

  return
  end

インクルードファイルfunc.inc

  integer i, j
  integer a, b, c

  common /mycom1/ i, j
  !$OMP THREADPRIVATE(/mycom1/)
  common /mycom2/ a, b, c
  !$OMP THREADPRIVATE(/mycom2/)

そして最後に C++ プログラムmain.cpp :

#include<iostream>
#include<sstream>
#include<vector>
using namespace std;

#include<omp.h>

extern "C"
{
  void set_(int*, int*);
  void func_(int*, int*);
};


int main(int argc, char *argv[])
{
  int nthread;
  {
    istringstream iss(argv[1]);
    iss >> nthread;
  }

  int n;
  {
    istringstream iss(argv[2]);
    iss >> n;
  }

  vector<int> a(n, -1);

#pragma omp parallel num_threads(nthread) shared(a)
  {
    const int this_thread = omp_get_thread_num();
    const int num_threads = omp_get_num_threads();

    const int m = n / num_threads;
    int start = m * this_thread;
    int end = start + m;

    const int p = n % num_threads;
    for (int i = 0; i < this_thread; ++i)
      if (p > i) start++;
    for (int i = 0; i <= this_thread; ++i)
      if (p > i) end++;

#pragma omp critical
    {
      cout << "#t " << this_thread << " : [" << start
           << ", " << end << "[" << endl;
    }

    set_(&start, &end);
    func_(&n, a.data());
  }

  cout << "[ " << a[0];
  for (int i = 1; i < n; ++i)
    cout << ", " << a[i];
  cout << "]" << endl;

  ostringstream oss;
  for (int i = 1; i < n; ++i)
    if ((a[i] - a[i - 1]) != int(4))
      oss << i << " ";

  if (! oss.str().empty())
    cout << "<<!!  Error occured at index " << oss.str()
         << " !!>>" << endl;

  return 0;
}
  • コンパイル手順 (gcc バージョン 4.8.1):

    gfortran -c func.f -fopenmp
    g++ -c main.cpp  -std=gnu++11 -fopenmp
    g++ -o test main.o func.o -lgfortran -fopenmp
    
  • 次のように起動できます。

    ./test 10 1000
    

    どこ

    • 最初の整数 (10) は、必要なスレッドの数です。
    • 2 番目 (1000) は 1 つのベクトルの長さです。

    このプログラムの目的は、このベクトルをスレッド間で分割し、各スレッドがその一部を埋めるようにすることです。

    ベクトルの塗りつぶしは、fortran 77 内で行われます。

    • setルーチンは、最初にスレッドによって管理される下限と上限を設定します。
    • funcルーチンは、前の境界の間のベクトルを埋めます。

通常、エラーがなく、共通の Fortran 77 ブロックが共有されていない場合、最終的なベクトルは 4 * k の値 (k は 1 から 1000 まで) で満たされます。

プログラムをキャッチできませんでした。逆に、 func.incの fortran 77 OMP ディレクティブを削除すると、共通ブロックはプライベートではなくなり、多くのエラーが発生します。

結論として、最初の問題を解決するために必要な唯一のことは、共通ブロックのすぐ後ろに OMP ディレクティブを追加することです。これは、すべてが 1 つのインクルード ファイル (私のテストのように) にまとめられているため、それほど複雑ではありません。

これが役立つことを願っています。

よろしくお願いします。

于 2015-08-26T16:02:02.277 に答える