次のプログラムを並列化しようとしていますが、配列を削減する方法がわかりません。そうすることは不可能ですが、代替手段はありますか?ありがとう。 (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;
}
はい、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;
私は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ページにある複素数削減の例にそのまま従っています。
並列バージョンは正常に動作しますが、おそらくパフォーマンスの問題があるため、調査していません。
「パフォーマンスの問題」というのは私自身のものであり、それらを導入しないことは完全に簡単です。
コードの変更部分は次のとおりです。
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}} )
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
ステートメントを記述する必要があるという魅力のない見通しが残ります。コードが学習に使用したい単なる例である場合は、続けてください。
しかし、もしあなたが純粋に並列接頭辞合計ルーチンを書くことを意図しているなら、あちこち検索してください。 これは開始するのに適した場所です。