OpenMPで配列のリダクションを効率的に行う方法

このQ&Aのポイント
  • 現在、OpenMPを用いてOpenCVのライブラリを並列実行可能な形にしていますが、配列のリダクションを行う部分で問題が発生しています。
  • データアクセスの競合を防ぐために「#pragma omp critical」という方法を試しましたが、処理速度が200倍遅くなってしまい、使い物になりません。
  • より効率的な配列のリダクション方法を知りたいです。OpenMPで配列のリダクションをする際の定石があれば教えていただきたいです。
回答を見る
  • ベストアンサー

OpenMPのことで悩んでいます

現在、OpenMPを用いてOpenCVのライブラリを並列実行可能な形にしています。 その中で、配列のリダクションを行う場所があるのですが、その部分をどう書き換えればいいのか悩んでいます。 for( i = 0; i < height; i++ )//画像の高さ { for( j = 0; j < width; j++ )//画像の幅 { if( image[i * step + j] != 0 ) //エッジ抽出後の画像データ for(int n = 0; n < numangle; n++ ) { int r = cvRound( j * tabCos[n] + i * tabSin[n] ); //極座標空間への変換 r += (numrho - 1) / 2; int index = (n + 1) * (numrho + 2) + r + 1; //#pragma omp critical //{ accum[index]++; //極座標に対応する配列accumへの軌跡の投票 //} } } } 上がそのコードです。配列accumの要素をインクリメントする際に、データアクセスの競合が起こってしまいます。 その部分の上に「#pragma omp critical」と入れればアクセスの競合は防げるのですが、速度が200倍程度遅くなってしまい、使い物になりません。 なんとか配列の要素に対するリダクション処理を行いたいです。 OpenMPで配列のリダクションをする際の定石等あれば教えていただきたいです。

質問者が選んだベストアンサー

  • ベストアンサー
  • ki073
  • ベストアンサー率77% (491/634)
回答No.4

if文がどの程度足を引っ張るか興味があったので、計算時間を測ってみました。 質問欄のプログラム、ただし、外側のループを20回実行して時間稼ぎをしています。 2.98秒 一番内側のループを外側に出したもの、 11.48秒 改造して共通する計算をできるだけループ外に出したもの 8.3秒 やっぱりif文の影響がかなり大きいようです。 ついでに、 No.1の方法 11.9秒 No.2の方法 8.7秒 ちなみに第一世代のCore i5で2.66GHz 4 coreです。 プログラムが正しいかほとんどチェックしていませんので、結果を保証しかねますのでその点はご了承ください。 結局は元の方法が一番速い結果でした。 accum[index]++がデータの依存関係が有りまくりなのが敗因のようです。 どうしても速くしたいのであれば、ここだけではなく全体のアルゴリズムを見直す必要があるようです。

p_t_r_n_
質問者

お礼

ありがとうございます。 私の方でもいろいろとやってみたのですが、やはりアルゴリズムの大幅な変更が必要だという結論になりました。 OpenMPにはアドレス単位のアトミック演算がないというのが痛いですね・・・。

その他の回答 (3)

  • ki073
  • ベストアンサー率77% (491/634)
回答No.3

考えてみましたが、No.2以上は難しそうです。 if文を考慮に入れないと600*800*180回ループを回りますので、それに比べると計算量がそんなに増える訳ではないので、まあ効果は期待できるのではないかと思います。CPUコアが2つだと苦しいような気がします。 変数がどのような値をとるのか分かりませんので 他の考え方ができるか判断できませんが、numrhoは定数じゃ有りませんよね。定数なら別のやり方も。 また、思い切って一番内側のfor文を一番外に出すのも面白いかも知れません。 それによって、if文が一番内側にありますので、当然これが足を引っ張ります。 cvRound( j * tabCos[n] + i * tabSin[n] )が cvRound( j * tabCos[n] )+cvRound( i * tabSin[n] )に置き換え可能なら、多くの計算をループの外に出せるようになります。 その辺りがどの程度効果があるか不明ですが、計算量はかなり減少させることができます。iやjに対してindexが単調増加になりますので、何か利用できる可能性もあります。if文のimage[i * step + j]もi*step分平行移動しただけですので、計算量減少に利用できるかも知れません。いずれにしても、大幅な改変が必要ですので、そこまでやる必要があるかも分かりません、 こちらは、そんな気がするという程度ですので、まだ深くは考えていません。

  • ki073
  • ベストアンサー率77% (491/634)
回答No.2

>クリティカルに加えるという事でいいのでしょうか。クリティカルな操作の回数を減らせるのですね。 そうです。 質問欄のものよりも速くはなるはずですが、openMPを使わない状態に比べて速くならないかもしれません。 height*width* numangle回criticalの発生が(if文が途中にあるのでもっと少ないはずですが)、height回まで減りますが、こんどは配列の初期化と加算が増えてしまします。 No.1の後半に書いている方法が良いように思います。 #pragma omp parallel for schedule(static, 1) for(k=0; k<NumThread; k++){ // increment_accum[]を確保して初期化、 int i_start=height/NumThread*k; int i_end=(k==NumThread) ? height : height/NumThread*(k+1); for(int i=i_start i < i_end; i++ ){ のようにして(iの範囲が正しいか確認してください)、 } //accum[]にincrement_accum[]を加える } のようにすると、NumThread回に減らせます。if文がありますので、heightを均等に分けても偏りが生じる可能性もあります。 その時は、NumThreadをCPUコア数ではなく、2~数倍多めにしておくとスレッドの偏りが減ります。 CPUコア数が4以上でないと、速度的には苦しいと思います。 高速化の基本は、回数が一番多い一番内側のループの最適化をまず考えて並列化に進むのですが、難しそうですね。 それと、 height, width, numangleの数とaccum[]の配列サイズ、 if( image[i * step + j] != 0 )が成立する大まかな比率はどれくらいになりますか? accum[]のサイズがあまり大きくなく、if文が成立する割合が比較的高い前提ですので、条件によっても高速化の手法が変わる可能性があります。

p_t_r_n_
質問者

補足

詳しくありがとうございます。 height = 600 width = 800 numangle = 180 accum[]のサイズは280000要素程度 ifが成立する比率は40%程度なのですが、どうでしょうか?

  • ki073
  • ベストアンサー率77% (491/634)
回答No.1

並列化が一番外のループで行われており、accum[index]++だけがデータ依存関係があるとして書きます。 その場合には、依存関係にある計算を、できるだけ外側のループに移せないか考えます。 そのままでは無理そうなので、 for( j = 0; j < width; j++ )の前に、increment_accum[]のような配列を確保して初期化、 accum[index]++の位置に、increment_accum[index]++ for( j = 0; j < width; j++ )ループを抜けたところで、accum[]にincrement_accum[]を加えることで、外に出せます。 これでも遅い場合は,並列実行されるCPU数に分割するために新たにループを作成して、increment_accum[]を確保、 for( i = 0; i < height; i++ )をCPU数に応じて分割し後は同様、 前者の方法で解決しそうな気がします。

p_t_r_n_
質問者

補足

widthループを抜けたとろでaccumにincrement_accumを加えるという事ですが、increment_accumはプライベートな配列であり、accumはグローバルな配列なため、クリティカルに加えるという事でいいのでしょうか。クリティカルな操作の回数を減らせるのですね。

関連するQ&A

  • OpenMPのリダクション演算を自分でする方法

    OpenMPでプログラムを並列化しているのですが、以下の内積を並列化する際にreduction演算をしなくてはなりません for( i=0; i<N; i++ ){  r += x[i] * y[i]; } ↓ #pragma omp for reduction(+:r) for( i=0; i<N; i++ ){  r += x[i] * y[i]; } このように、for文に対してreduction節を入れてスレッド並列化するのが普通ですが、ここでBLASというライブラリを使いたいので、reductionを自分でしたいと考えています int INC = 1; int n = i_end - i_start; for( i=i_start; i<i_end; i++ ){  r += ddot_( &n, &(x[i_start]), &INC, &(y[i_start]), &INC ); } /* reduction here */ 0~N-1までの配列の要素を、自分でスレッドに分けて、i_start~i_endまでの要素の内積を各スレッドがddot_関数を使って計算します その後、reductionの計算をする必要があるのですが、一体どうすればreduction演算ができるのか分かりません どなたか分かる方、ご教授ください なお、スレッド並列版のBLASは使わない予定です あくまで、自分でスレッド並列化を行ない、その中で逐次版BLASを呼び出すという方向で考えています

  • VC++上でのOpenMPの使用に関しての質問です

    VC++2010でOpenMPを使い始めて間もないので、初歩的な質問かもしれませんが、検索しても解決できなかったため、ここで質問させてください。 以下のプログラムについてです。 これを実行すると "lCount=10001"が出力されるはずですが、 "lCount=5000"とか10001以外の数値が出力されてしまいます。 原因が分からず困っています。よろしくお願いします。 ちなみに、クラスCTest は何もしていませんが、以下のいずれかを 行うと"lCount=10001"が出力されるようになります。 ・07~09行目をコメント ・24行目をコメント ========================================= 01>#include <stdio.h> 02>#include <omp.h> 03> 04>class CTest 05>{ 06>public: 07> CTest() 08> { 09> } 10> 11> long m_lTT; 12>}; 13> 14>void main() 15>{ 16> long lCount = 0; 17> long i; 18> 19> ::omp_set_num_threads(10); 20> 21> #pragma omp parallel for 22> for(i = 0; i <= 10000; i++) 23> { 24> CTest aa[100]; 25> #pragma omp critical 26> { 27> lCount++; 28> } 29> } 30> 31> printf("lCount=%d\n", lCount); 32> _fgetchar(); 33>} =========================================

  • OpenMPによる並列処理で質問があります。

    c言語でポアソン方程式を差分法で解くプログラムを作成し、それをOpenMPで並列化して、スレッド数を1,2,4,8と増やしながら処理時間の計測をしました。  スレッド数が2の時は処理時間が短くなったのですが、4,8と増やしていくごとに処理時間が逆に増えてしまいました。デュアルコア4プロセッサなのでコア数以上のスレッド数ではないはずなのですがこれはなぜなんでしょうか? 計測した解析領域は 48×48 72×72 の二つです。どちらも4、8スレッドの時は遅くなってしまいました。以下に並列化部分のソースを載せます。並列化した場所は、連立方程式を解く部分で、求解には、ガウス・ジョルダン法を用いています。並列化でまずいところがあればそちらも指摘お願いします。 for(k=0;k<=size-1;k++) { pivot=a[k][k]; #pragma omp parallel for for(j=0;j<=size-1;j++) { a[k][j]=a[k][j]/pivot; } b[k]=b[k]/pivot; #pragma omp parallel for private(j,tmp) for(i=0;i<=size-1;i++) { tmp=a[i][k]; if(i!=k) { for(j=0;j<=size-1;j++) { a[i][j]=a[i][j]-(tmp*a[k][j]); } b[i]=b[i]-(tmp*b[k]); } } }

  • CでOpenMP、パラレル内での共有変数の宣言方法

    C言語でOpenMPを利用したとき、parallel構文内で、共有変数を宣言する方法はありますか? OpenMPを利用して、スレッド並列にしたプログラムを書いています。 #pragma omp parallel { ~~ ~~ } この、~~の部分で、大きく分けて二つの処理をしているので、関数に分けました。 #pragma omp parallel private( a, b, c, d, e, f, g, h, i, j ) { func1( a, b, c, d, e, f, g, h ); func2( a, b, c, d, e, f, g, h, i, j ); } このとき、2つ目の関数で共有変数を複数使う必要があります。 しかし、共有変数の数は多く、引数にするとかなりの数の引数になってしまいます。 そこで、できればfunc2()という関数の中で、スレッドで共有できるshared変数を宣言したいのですが、方法がわかりません。 どなたか、知っている方教えてください。

  • 非常にはずかしい質問ですが 配列の質問です。

    C言語の勉強で時間が経つごとに、肝心の基礎が忘れがちになるのですよね。 それに、配列に対しての説明ってほとんど文字列ばかりで整数はみつからないです。 今回の質問は ・int型配列の要素ひとつずつ代入するにはどうするか? ・同じ数字を代入させないにはどうするか? ・配列中n個の要素を表示させるにはどうするのか? 条件 1、配列の要素はn個 2、同じ数は× 一応かいてみました。 { int buffer[6], i, j, signal ; srand((unsigned int)time(NULL)); for( i = 0 ; i < 6 ; i++){ buffer[i] = rand()%42+1; for( signal = 0 , j = 0 ; j < i ; j++ ){ if(buffer[i] == buffer[j]){signal = 1; break;} } if(signal == 0){break;} } printf("%d\n",buffer);

  • 2番目の最大値を求める

    n個の要素をもった配列で、2番目に大きい要素を見つけるコードを書きたいのですが、合っているのか不安になって、質問させてもらいます。 アルゴリズムとしては、まず、n-1回の比較をしてその中でmaxを見つけ、残りのn-1個の要素のなかでn-2回の比較をしてそのn-1個の配列のmaxを見つければ、2番目に大きい要素を見つけられると考えました。 int main(void) { int E[n]; int i, j, temp; for(i=0;i<N;i++){ for(j=1;j<N-i;j++){ if(E[j-1]>E[j]){ temp=E[j-1]; E[j-1]=E[j]; E[j]=temp; } } } return E[j]; } これであっていますでしょうか? よろしくお願いいたします。

  • 【C言語・BLAS】 行列ベクトル積におけるエラー

    今、BLASを使って行列ベクトル積を計算するのにどれくらい時間がかかるか計測しようとしています。 しかし、短いプログラムにも関わらずC用インターフェースのスレッド並列版BLASを呼び出す部分でFloating point exceptionエラーが出てしまいます。 なぜエラーが出るのか全く分からないので、どなたか分かる方ご意見いただけないでしょうか? #define SEED 1 #define N 100 /* extern void dgemv(char transa, int m, int n, double alpha, double *a, int lda, double *x, int incx, double beta, double *y, int incy); */ int main( int argc, char *argv[] ){  char TRANS = 'T';  int INC = 1;  double ALPHA = 1.0;  double BETA = 0.0;  int i, j, n1, n2;  double **matrix;  double *vector;  double *result;  double start_time;  double end_time;  fprintf( stdout, "____performance evaluation start____\n" );  srand(SEED);  matrix = Malloc2DDouble( N, N );  vector = (double *) malloc ( sizeof(double) * N );  result = (double *) malloc ( sizeof(double) * N );  n1 = N; n2 = N;  #pragma omp parallel  {   #pragma omp for private(j)   for( i=0; i<n1; i++ ){    for( j=0; j<n2; j++ ){     matrix[i][j] = ( ( rand() / (double)RAND_MAX ) - 0.5 );    }    vector[i] = ( ( rand() / (double)RAND_MAX ) - 0.5 );    result[i] = 0.0;   }  }  start_time = GetTime();  dgemv( TRANS, n2, n1, ALPHA, matrix[0], n2, vector, INC, BETA, result, INC );  end_time = GetTime();  return EXIT_SUCCESS; }

  • Javaの二次元配列についてです

    配列要素を 1, 2, 3, 4, 5 2, 2, 3, 4, 5 3, 3, 3, 4, 5 4, 4, 4, 4, 5 5, 5, 5, 5, 5 のようにしたいのですがどうすればよろしいでしょうか? int[][] a = new int[5][5]; for (int i = 0; i < a.length; i++) { for (int j = 0; j < a[i].length; j++) { ~ここの処理を教えてください~ } }

    • ベストアンサー
    • Java
  • Cで回転プログラムの高速化を

    Cで、90枚の画像を、それぞれ0度から90度まで回転させるプログラムを作りました。 回転の処理は重く時間がかかるため、 それぞれの角度の回転後の位置を配列に格納して、 その値を参照して画像を回転させていこうと思ったのですが、 例えば、画像が300*300としたら回転座標を入れる配列は2次元で2つとりますよね? kaitenX[90][300] kaitenY[90][300] これで、例えば元画像を50度回転させる時のx,y座標が10,50の所の回転後の座標は kaitenX[50][10]の値(回転後のx座標の値) と kaitenY[50][50]の値(回転後のY座標の値)を 参照さしてやればいいと思うのですが、 このとき、回転後の画像をtemp[300][300]という回転後の出力用配列に 代入していくとき、 どうやって代入していけばよいでしょうか? まさか、 for(r=0;r<90;r++) //rは角度情報 for(i=0;i<300;i++) for(j=0;j<300;j++) { temp[i][j] = Gengazo[r][kaitenx[r][j]][kaiteny[r][i]] } なんて風には書けませんよね^^;(自分で一応試したけどダメでした) 回転後の座標を格納するまでは出来ると思うのですが、 格納後、どうやってその値を参照させて、格納させていけばいいか わかりません。 教えていただけませんでしょうか? また、もっと高速化できる効率の良い方法があれば教えて下さい。

  • -fopenmpのコンパイルが通らなくなりました。

    とても困っているのでどなたかご存知の方、教えてください。 MACAir OS X 10.8.5を使っています 今まで普通にgccでコンパイルできていたのですが、 急にコンパイルエラーが出るようになりました。 思い当たる節としては、X-codeを新しいバージョン(5.0)にしたことです。 以下のようにOpenmpを実行するためにコンパイルしました。 $gcc -O hello_omp1.c -fopenmp ld: library not found for -lgomp clang: error: linker command failed with exit code 1 (use -v to see invocation) 以下は、hello_omp1.cの中身です。 #include<stdio.h> #ifdef _OPENMP #include<omp.h> #endif int main() { #ifdef _OPENMP printf("procs=%d\n",omp_get_num_procs()); printf("max #threads=%d\n",omp_get_max_threads()); #endif #ifdef _OPENMP #pragma omp parallel { printf("Hello world %d of %d\n", omp_get_thread_num(),omp_get_num_threads()); } #else printf("Helloworld\n"); #endif return 0; }

専門家に質問してみよう