web-dev-qa-db-ja.com

OpenMPでの配列の削減

次のプログラムを並列化しようとしていますが、配列を削減する方法がわかりません。そうすることは不可能ですが、代替手段はありますか?ありがとう。 (mの削減を追加しましたが、これは間違っていますが、それを行う方法についてアドバイスが必要です。)

#include <iostream>
#include <stdio.h>
#include <time.h>
#include <omp.h>
using namespace std;

int main ()
{
  int A [] = {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};
  int S [10];

  time_t start_time = time(NULL);
  #pragma omp parallel for private(m) reduction(+:m)
  for (int n=0 ; n<10 ; ++n ){
    for (int m=0; m<=n; ++m){
      S[n] += A[m];
    }
  }
  time_t end_time = time(NULL);
  cout << end_time-start_time;

  return 0;
}
27
user2891902

はい、OpenMPを使用して配列の縮小を行うことができます。 Fortranでは、このための構成もあります。 C/C++では、自分で行う必要があります。以下に2つの方法を示します。

最初の方法は、スレッドごとにSのプライベートバージョンを作成し、それらを並列に入力してから、クリティカルセクションのSにマージします(以下のコードを参照)。 2番目の方法は、10 * nthreadsの次元を持つ配列を作成します。この配列を並列で埋めてから、クリティカルセクションを使用せずにSにマージします。 2番目の方法ははるかに複雑で、特に注意しないとマルチソケットシステムでキャッシュの問題が発生する可能性があります。詳細については、これを参照してください クリティカルセクションを使用せずにOpenMPと並行してヒストグラムを埋める(配列の縮小)

最初の方法

int A [] = {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};
int S [10] = {0};
#pragma omp parallel
{
    int S_private[10] = {0};
    #pragma omp for
    for (int n=0 ; n<10 ; ++n ) {
        for (int m=0; m<=n; ++m){
            S_private[n] += A[m];
        }
    }
    #pragma omp critical
    {
        for(int n=0; n<10; ++n) {
            S[n] += S_private[n];
        }
    }
}

第二の方法

int A [] = {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};
int S [10] = {0};
int *S_private;
#pragma omp parallel
{
    const int nthreads = omp_get_num_threads();
    const int ithread = omp_get_thread_num();

    #pragma omp single 
    {
        S_private = new int[10*nthreads];
        for(int i=0; i<(10*nthreads); i++) S_private[i] = 0;
    }
    #pragma omp for
    for (int n=0 ; n<10 ; ++n )
    {
        for (int m=0; m<=n; ++m){
            S_private[ithread*10+n] += A[m];
        }
    }
    #pragma omp for
    for(int i=0; i<10; i++) {
        for(int t=0; t<nthreads; t++) {
            S[i] += S_private[10*t + i];
        }
    }
}
delete[] S_private;
28
Z boson

私はZbosonの答えに関して2つの発言をしています:
1。方法1は確かに正しいですが、#pragma omp criticalのためにリダクションループが実際にシリアルに実行されます。これはもちろん、部分行列が各スレッドに対してローカルであり、対応する行列に起因するスレッドによって削減する必要があります。
2。方法2:初期化ループは単一セクションの外側に移動できるため、並列化可能になります。

次のプログラムimplements配列削減openMP v4.0ユーザー定義削減機能を使用

/* Compile with:
     gcc -Wall -fopenmp -o ar ar.c
   Run with:
     OMP_DISPLAY_ENV=TRUE OMP_NUM_THREADS=10 OMP_NESTED=TRUE ./ar
*/
#include <stdio.h>
#include <omp.h>
struct m10x1 {int v[10];};
int A [] =       {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};  
struct m10x1 S = {{ 0,  0,  0,  0,  0,  0,  0,  0, 0,  0}};
int n,m=0;

void print_m10x1(struct m10x1 x){
  int i;
  for(i=0;i<10;i++) printf("%d ",x.v[i]);
  printf("\n");
}

struct m10x1 add_m10x1(struct m10x1 x,struct m10x1 y){
  struct m10x1 r ={{ 0,  0,  0,  0,  0,  0,  0,  0, 0,  0}};
  int i;
  for (i=0;i<10;i++) r.v[i]=x.v[i]+y.v[i];
  return r;
}

#pragma omp declare reduction(m10x1Add: struct m10x1: \
omp_out=add_m10x1(omp_out, omp_in)) initializer( \
omp_priv={{ 0,  0,  0,  0,  0,  0,  0,  0, 0,  0}} )

int main ()
{
  #pragma omp parallel for reduction(m10x1Add: S)
  for ( n=0 ; n<10 ; ++n )
    {
      for (m=0; m<=n; ++m){
        S.v[n] += A[m];
      }
    }
  print_m10x1(S);
}

これは、 OpenMP 4.0の機能 の97ページにある複素数削減の例にそのまま従っています。

並列バージョンは正常に動作しますが、おそらくパフォーマンスの問題があるため、調査していません。

  1. add_m10x1の入力と出力は値で渡されます。
  2. Add_m10x1のループはシリアルで実行されます。

「パフォーマンスの問題」というのは私自身のものであり、それらを導入しないことは完全に簡単です。

  1. add_m10x1へのパラメーターは、参照によって(Cのポインター、C++の参照を介して)渡す必要があります
  2. add_m10x1の計算は適切に行われるべきです。
  3. add_m10x1はvoidと宣言し、returnステートメントを削除する必要があります。結果は最初のパラメーターを介して返されます。
  4. それに応じて、declareプラグマを変更する必要があります。コンバイナは、割り当てではなく関数呼び出しのみである必要があります(v4.0 specs p181 lines 9,10)。
  5. add_m10x1のforループは、omp parallel forプラグマを介して並列化できます。
  6. 並列ネスティングを有効にする必要があります(例:OMP_NESTED = TRUE経由)

コードの変更部分は次のとおりです。

void add_m10x1(struct m10x1 * x,struct m10x1 * y){
  int i;
  #pragma omp parallel for
  for (i=0;i<10;i++) x->v[i] += y->v[i];
}

#pragma omp declare reduction(m10x1Add: struct m10x1: \
add_m10x1(&omp_out, &omp_in)) initializer( \
omp_priv={{ 0,  0,  0,  0,  0,  0,  0,  0, 0,  0}} )
9
NameOfTheRose

OpenMPリダクション操作で配列を使用できるFortranにコードを変換しても魅力がない場合は、一時変数の束を使用できます。例えば

int S0, S1, S2, ..., S9;
...
#pragma omp parallel for private(...) shared(S0, S1, S2, ..., S9) \
            reduction(+:S0, S1, S2, ..., S9)
for ...

これにより、どの一時ファイルを更新するかを決定するために、何らかの種類のifまたはcaseステートメントを記述する必要があるという魅力のない見通しが残ります。コードが学習に使用したい単なる例である場合は、続けてください。

しかし、もしあなたが純粋に並列接頭辞合計ルーチンを書くことを意図しているなら、あちこち検索してください。 これは開始するのに適した場所です。