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

このQ&Aのポイント
  • C言語でOpenMPを利用したとき、parallel構文内で、共有変数を宣言する方法はありますか?
  • OpenMPを利用して、スレッド並列にしたプログラムを書いています。関数に分けているため、共有変数を引数に渡すのは手間です。func2()内でスレッドで共有できるshared変数を宣言したいのですが、方法がわかりません。
  • どなたか、C言語でOpenMPを利用してパラレル内で共有変数を宣言する方法を知っている方、教えてください。
回答を見る
  • ベストアンサー

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変数を宣言したいのですが、方法がわかりません。 どなたか、知っている方教えてください。

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

  • ベストアンサー
  • mktw00w
  • ベストアンサー率100% (1/1)
回答No.5

int *p; #pragma omp single copyprivate(p) { p = (int *) malloc ( size ); } これで多分いいと思います。 値がコピーされるので、同じアドレスを指すポインタになるはずです。

mist_to
質問者

お礼

まだ試してませんが、ありがとうございます これで一度やってみます

その他の回答 (10)

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

たびたびすみません。 途中で大きな共有領域を確保するには、No.5のmktw00wさんの回答が正解のようですね。 質問者さんの質問の意図が正しく理解できていませんでした。すみませんでした。

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

No.8のお礼欄をよく読むとこの部分のopenMP化はできているわけですね。 まだできていないと勘違いしていました。ピンぼけ回答になってすみません。 リファクタリングなのでしょうか。さらなる高速化が必要なときに再チャレンジでどうでしょうか。 profilerの結果を見ながらベクトル化やCUDA化も結構おもしろいですよ。データ依存性を解決するのに手間どっておりますが。

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

問題点承知しました。 今までの議論の結果、並列化領域外でポインタなり配列なりで領域を確保して、必要に応じて関数に渡してやるしか無いように思います。No5の方法についても、ポンタはthread分確保されますが、共有領域を指すポインタなので、変更すると変数領域を見失うので変更が事実上できないように思います。(ここでの使い方限定ですが) 外で宣言、確保するのと同じようなことになるような気がします。 No.1とNo2の回答に戻ってしまいますが、共有変数に書き込むときにthread間のデータ依存性が問題になることが多々有りますので注意してください。 並列化をしたい領域はもともとはループになっていなかったのでしょうか? ループ部分をopenMPでの並列化により高速にしたいというとが多いと思いますが。 並列化領域を関数に書き換えたのは大正解だと思います。private宣言を減らせるし、うっかり忘れてsharedになっているのに気がつかないことはよくあります。私もやってしまいます。

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

No.7のお礼欄について private宣言した変数のコピーのことは書いた後で怪しいなと思ったのですが、質問さんの指摘のとおりだと思います。訂正します。 #pragma omp parallel for private(i)の場合は、空の変数i領域を確保するだけではなくて、openMPが中身を入れてやらないといけませんので、private宣言でそうなるのではなく、forがやっているようですね。 いずれにしても、privateの場合は、openMPはthread分の新たな領域を確保するという特別なことをやっています。 ただし、中で宣言したローカル変数は、openMPが特別なことをしているのではなく、Cのローカル変数の機能であると考えるのが自然ではないこと思います。並列化領域内部から外部の関数を呼び出す場合は、thread safeであればopenMPのことを外部の関数が知らなくても期待通りに動きますので。 それよりも何が問題なのでしょうか。 No.2のお礼欄ではMPIとOpenMPは正常に動いているような書き方なのですが。 並列化するときの構造上のことを少し考えないといけないように思うのですが、どうでしょうか。

mist_to
質問者

お礼

#pragma omp parallel private としたときは、OpenMPがスレッド生成と同時にプライベート変数を宣言します。 parallelリージョン内で変数を宣言した場合は、各スレッドが各々でプライベート変数を宣言したことになります。 この二つはOpenMP上での動作が少し異なりますが、結果として同じプライベート変数となります。 ですから、問題はparallelリージョン内で共有変数を宣言したいのにできない、ということです。 元々は関数に分けず長大なプログラムをつらつらと書いてあって、 parallelリージョンの外で共有変数を宣言してから使っていたのです。 それを関数に分けたものですから、 外で宣言した共有変数を引数として持ち込むよりも内部で共有変数を宣言したくなったのです。

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

No.6のお礼欄について {}の中のthreadが複数できますので、ローカル変数iがthread分別々に確保されるということですのでopenMPでいうpriveteではないはずです。ごく普通のローカル変数の確保で、たまたまthreadが複数できただけで、関数の中で宣言するのと同じ挙動のはずです。 forの直前に#pragma omp parallel for private(i)と宣言した場合(ここではprivate宣言は省略可能ですが)には、iの値をprivate変数iにコピーされthreadが作られます。しかしNo.6のお礼欄の場合は、iが新しく確保されるだけで値はコピーされない。 実質的にはprivateと似ていますが、この解釈の方が自然だと思いますが如何でしょうか?

mist_to
質問者

お礼

#pragma omp parallel for private(i) だと、iの値はコピーされませんよ。 firstprivateならコピーされますが。 単にスレッドごとで新たな変数iがスレッドごとに宣言されるだけです。 OpenMP Application Program Interface version 3.0 を読んでいただければ分かりますが、 リージョン内から呼び出されたルーチンで宣言された変数は、基本的にプライベートとなります。 同じparallelリージョンに結合しているtaskリージョンのセットにおいて、 それぞれのtaskリージョンが同じ名前で記憶域の異なるブロックにアクセスする変数、というのがプライベート変数ですので、この場合も正しくプライベート変数のはずです。

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

No.4でポインタを作る方法を回答されていますので、openMPのsharedについて補足しておきます。 sharedは、openMP自体は何も特別のことをしないので、自然と他のthreadから見えると言うことです。 >#pragma omp parallelの中で変数の宣言を行うと、 >問答無用でプライベート変数になってしまいます。。。 多分関数の中での変数の宣言だと思うのですが、openMPでいうprivateではなくCのローカル変数ですのでローカル変数としての振る舞いをしているだけです。

mist_to
質問者

お礼

#pragma omp parallel { int i; for( i=0; i<3; i++ ){ printf("%d : i = %d\n",omp_get_thread_num(), i); } } 例えば、こういう風にして変数iを宣言しますと、iはプライベートの変数になって、 0 : i = 0 0 : i = 1 0 : i = 2 1 : i = 0 1 : i = 1 1 : i = 2 みたいな出力になりますよね?? sharedだとスレッドごとに同じiを参照してしまってこうはなりませんし

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

No.3のお礼欄について 質問者さんのプログラムが既にopenMPで正常に動いているのなら、shared宣言は省略可能ですので省略すれば良いと思います。 thread間のデータ共有や依存性の問題がでるのでしたら別に考える必要がありますが。

mist_to
質問者

お礼

えっと、その共有変数をどうやって宣言すればいいのでしょう?? #pragma omp parallelの中で変数の宣言を行うと、問答無用でプライベート変数になってしまいます。。。

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

No.2の訂正です。 >動的に確保した領域のポイタ値を共有変数に書き込めがよいのです。 >が、おもいっきりthread間でデータの依存関係がでるように思います。 ちょっと不正確な記述でした。 動的に配列を確保した場合には領域を解放すると、ポインタの値は残るが中身は書き換えられることがあるので当然値は保証でできない。解放される前に(threadを止めて)他のthreadから読み込まれないと値の保証はできない。並列化処理でよく言うデータの依存関係とは異質のものでした。 ともかく 1) 並列化処理ではなく、ループに書き換えて同じ機能を出せるか。 を考えてみてください。

mist_to
質問者

お礼

えっと、そこは問題ないです 記述はしてませんが、MPIとOpenMPのハイブリッド並列でして、 逐次の場合とハイブリッド並列の場合とで結果が同じであることは確認済です

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

No.1のお礼欄について >その共有変数というのは複数の配列でして、かつfunc2内でしか使わない変数なのです。 >なので、できればfunc2内でポインタを宣言し、 >動的にメモリ確保して、func2内で解放したいのです。 動的に確保した領域のポイタ値を共有変数に書き込めがよいのです。 が、おもいっきりthread間でデータの依存関係がでるように思います。どうなんでしょうか? 次のことを考えてみてください 1) 並列化処理ではなく、ループに書き換えて同じ機能を出せるか。 2) ループの回る順番を変えても同じ結果になるか。 どうも両方とも駄目なように思いますがいかがですか? openMPではなく素直に子プロセスを作って実行して、親からコントロールするのが適しているように思いますが、情報があまりにも少なすぎますので。

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

普通のCの書き方で問題ないはずです。配列や構造体に共有変数をまとめてfunc2に参照渡しにすればどうしょうか。 並列化区間のthreadの実行順序は不定ですので、共有変数に並列化区間で書き込む時は十分注意してください。

mist_to
質問者

お礼

回答ありがとうございます。 その共有変数というのは複数の配列でして、かつfunc2内でしか使わない変数なのです。 なので、できればfunc2内でポインタを宣言し、 動的にメモリ確保して、func2内で解放したいのです。 もちろん、構造体にまとめればできるのは分かっていますが。。。すみません。

関連するQ&A

  • -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; }

  • 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のリダクション演算を自分でする方法

    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を呼び出すという方向で考えています

  • C言語の基本的な質問ですが、関数へのポインタの宣言

    関数へのポインタの質問です。 下のように、関数へのポインタを使ったプログラムを書きました。 (関数へのポインタを理解するためのものなので、実用的な意味はありません。(*^_^*) また、このプログラムはコンパイルもリンクも実行も問題なく出来ます。) #include <stdio.h> int add_func(int,int); (*func_p0) (int,int); int main(void) { int (*func_p1) (int,int); int (*func_p2) ( ); int hoge0,hoge1,hoge2; func_p0=add_func; hoge0=func_p0(3,5); printf("0 : 3+5は%d\n",hoge0); func_p1=add_func; hoge1=func_p1(3,5); printf("1 : 3+5は%d\n",hoge1); func_p2=add_func; hoge2=func_p2(3,5); printf("2 : 3+5は%d\n",hoge2); return(0); } int add_func(int x, int y) { return(x+y); } func_p0のように戻り値の型を書かない場合と、func_p1やfunc_p2のように戻り値の型を書くのとでは何が違うのでしょうか。 func_p0は外部変数ですが、自動変数にする(main関数の中で同様に宣言。)とコンパイルエラーになります。 それはなぜですか。 func_p1のように引数の型が書いてあるのと、func_p2のように引数の型が書いていないのでは何が違うのでしょうか。 int (*func_p2) ( );というのは、int (*func_p2) (void);とは違うんですよね?

  • Cのローカル変数でstatic以外の使い方?

    C言語の課題について教えてください [課題] 以下の関数がある。各関数の引数、変数は自由に設定していい ・int main() ・void func() ・Point *get() { /* 構造体のアドレスを返す */ } ・構造体 typedef struct { int x; int y; int h; int w; }Point; 問題 main関数から、func関数を経由して、get関数を経由し値を取得し、表示する 以下が考えたソースになりますが、これだと、 ローカル変数でstaticを使っているので、get関数が固定値ではなく、 取得のたびに値が変わるような場合には、だめだといわれました。 考えたのですがよくわからないので、どういう場合に駄目なのかと、 どのように修正すればいいのか教えてください。 #include <stdio.h> typedef struct { int x; int y; int h; int w; }Point; void func(Point **); Point *get(); int main(void){ Point *get; func(&get); printf("get.x:[%d]\n",get->x); printf("get.y:[%d]\n",get->y); printf("get.h:[%d]\n",get->h); printf("get.w:[%d]\n",get->w); return 0; } void func(Point **pw){ *pw = get(); printf("Wrapper: pw==%p\n",pw); } Point *get(void) { static Point pget; pget.x = 2; pget.y = 2; pget.h = 30; pget.w = 40; return &pget; }

  • 変数を複数のモジュール間で共有する方法

    VBAで大きなプログラムを書いた際に、 後から修正したりしやすくするために、機能ごとにモジュールに分けて保存しておきたいと考えています。 例えば、 sub call start(a, b, c, d, e, f, ,・・・・・) call first(a, b, c, d, e, f, ,・・・・・) call second(a, b, c, d, e, f, ,・・・・・) ・・・・・ end sub のようにcallで繋いでいけば、複数のモジュールに機能を分散させることができるのですが、 変数をいちいち、括弧内に入れる必要があり、 後から修正するのが大変です。 sub call start() call first() call second() ・・・・・ end sub のように変数を書かずにcallを使いたいのですが、 複数のモジュール間で変数を共有することってできないのでしょうか? また、Excelのデータシートには、データ→グループ化という機能がついていますが こういうような機能はVBAにはないのでしょうか?

  • C++ visualstudio グローバル変数

    C++のプログラミングに関する質問です。 visual studio2008を使用しています。 手元に非常に複雑なC++ファイルなどから構成されている1つのprojectがあります。(ヘッダファイルなどもあります) そのため、このprojectをデバッグすると、A.cppファイルをデバッグ→B.cppファイルをデバッグ→A.cppファイルをデバッグ→C.cppファイルをデバッグのように、色々なcppファイルを跨ってデバッグします。 1つのcppファイルには大量の関数があり、cppファイルの先頭や,関数外の部分に例えばA.cppで int test などと宣言すれば、A.cppファイル内ではtestと名付けた変数をすべての関数で使えます。 しかし、あくまでA.cppファイル内だけで使用できるだけで、別のBやC.cpp内で使うことはできません。 このtest変数をB.cppやC.cppファイルでも使えるようにするにはどうすればいいでしょうか? 上で挙げた例でいうなら、A.cppからB.cppファイルに移動する際の関数の引数として渡す方法は考えられますが、実際には、 test変数を使いたいのはかなり後に登場するcppファイルで関数の引数として扱うのは非常に大変です。 (Z.cppでtest変数を使いたい。しかしZ.cppに到達するまでにはA~Y.cppを通り、その間に登場するすべての関数でtestを引数にしなければならない) 関数の引数でtest変数を使えるようにする以外の方法がありましたたら教えていただけると幸いです。 実際にやりたいことは A.cpp内にある関数が実行されたらその数をカウントし(A.cppのこの関数は何度も呼び出される関数) //count ++ このcount++の値によってZファイルのある関数での動作を変えたいと思っています。 if(count<100){ printf("aaa"); } しかし、現状ではZ.cppファイル内ではcountが定義されていないので上のようなif文を書くとエラーになってしまいます。

  • C言語 プロトタイプ宣言

    分割コンパイルした場合のプロトタイプ宣言について質問です。 以下のプログラムをコンパイルすると警告がでます。 プロトタイプ宣言は関数を利用する側と定義側両方に必要と理解していたのですが・・・ どなたか教えていただけますでしょうか。 windows7 cygwin gccでコンパイル エラーメッセージ $ gcc -o testMain.exe testMain.c testKioku.c testKioku.c:9: 警告: conflicting types for 'func1' testKioku.c:3: 警告: previous declaration of 'func1' was here testKioku.c:17: 警告: conflicting types for 'func2' testKioku.c:4: 警告: previous declaration of 'func2' was here ソース testMain.c #include <stdio.h> void func1(void); void func2(void); int cnt=5; main(){ printf("main=%d\n",cnt); func1(); func2(); } testKioku.c #include <stdio.h> void func1(void); void func2(void); extern int cnt; func1() { cnt++; printf("func1 global cnt=%d\n",cnt); func2(); } func2() { printf("func2 global cnt=%d\n",cnt); }

  • VBで、関数を関数の引数にするは?

    VBで関数の引数に、関数を呼ぶにはどのようにすればいいでしょうか?  例えば以下のようなイメージです   DEF FUNC1(X)=X^2   DEF FUNC2(F,a,b)=F(a)+F(b)     関数FUNC2では、関数Fを引数にする     つまりFUNC2(FUNC1,1,2)は     FUNC1(1)+FUNC1(2)=1^2+2^2=5 こんな感じです。 お教えください。

  • 関数の掛け算を返す関数

    いま関数の積分を行おうとしています. この積分を行う関数が double integral(double (*func)(double), double a, double b ) {    double ans;    .....    return ans; } となっていて,aからbまでfuncが指す関数を積分して結果を返します. 積分をさせる関数は double f1(double x) { return 3.0*x; } double f2(double x) { return x*x; } となっていて,同じようにg1, g2も用意します.(本当は関数が3つ4つあります.) 例えばf1を積分したいとき, int main() {    double ans = integral(f1, a, b);    printf("%f\n", ans); } ですよね. 自分で積分する関数を選ぶときは,ここに配列で場合分けをして double (*func_f[])(double)={f1, f2}; scanf("%d", &flag); /* flagに0~1を代入 */ ans = integral(func_f[flag], a, b); でいいと思います. さて,そこで本題なのですが, double (*func_f[])(double)={f1, f2}; double (*func_g[])(double)={g1, g2}; としておいて, scanf("%d", flag1); scanf("%d", flag2); func_f[flag1], func_g[flag2]; として関数を入力側から決定して, (例えばflag1=1, flag2=1,であったとして *func_f[1]=x*x *func_g[1]=5.0*x ならば) h(x)=(x*x)*(5.0*x)=5.0*x*x*x という関数を作って, h(x)を指すポインタを double (*h_ptr)(double) とすれば ans = integral(h_ptr, a, b); としたいのです. (f1*g2)を一つの関数としてh(x)=(f1*g2)(x)というように扱うことができればいいと思うのですが. integralの引数で,関数のポインタを2個にすると,汎用性が失われてしまうと思うので,できればそこは変えたくないです. どのようにすればよいのでしょうか? また,「考え方を変えればよい」などの意見も頂きたいです. 皆様,どうぞよろしくお願いします.