OpenMPdeマルチスレッド化した | 入門者フォーラム | フォーラム

iSUS に投稿されている記事の内容や、IA プラットフォーム上でのソフトウェア開発に関するトピックを開発者同士でディスカッションできる場としてフォーラムを公開しています。

運営ルールはフォーラムの利用案内をご覧ください。
フォーラムの使い方はこちら

 
フォーラムに投稿するにはログインが必要ですログイン

OpenMPdeマルチスレッド化した

メンバー投稿

8:34 AM
2012年4月17日


yoshihingis

投稿数 54

1

投稿は 8:40 AM – 2012年4月17日 に yoshihingis さんにより更新されました


非常に基本的な質問ですいません。
OpenMPでマルチスレッド化したコードをシングルコアCPUで動作させた場合、なにか不都合は生じるものでしょうか?

(すいません、入門者ディスカッションにも投稿してしまいまして、そちらは削除しようと思ったのですが、削除機能が無いようで、マルチポストになってしまうかもしれませんが、また慌てたのでタイトル名がおかしくなってしまいました。タイトルは投稿後は修正できないようですね。すいません。)

 OpenMPで4コアCPU用にomp_set_num_threads(4)で4スレッド化してparallel forにて4並列化したコードを作成したとします。
 このコードを古いシングルコアのCPUで4スレッド設定のまま使用した場合、何か不都合は生じるものなのでしょうか?

 個人的にはOpenMPでマルチスレッド化しても、CPUのスケジューラの方で、シングルコアCPUの場合は、4つに割り振れないので、特に問題は生じないと思っておりますが、考えが間違っていましたらご指摘いただければ幸いです。

 CPUの実コア数を把握して、それをスレッド数に反映するコードを書くのが理想だとは思いますが、古いシングルコアCPU搭載のPCと最新のマルチコアCPU搭載のPCでソフトを流用する場合もありますので、質問させていただいた次第です。wo

11:04 AM
2012年4月17日


iSUS編集部 – 菅原

投稿数 206

2

yoshihingis 様

明示的にソース中でomp_set_num_threads(4)でスレッド数を設定したり、
OMP_NUM_THREADS=4のように環境変数でスレッド数を指定して、シングルコアでバイナリーを実行すると、1つの物理コアを4つのスレッドが取り合う、オーバーサブスクライブの状態になります。

ただし、OMP_DYNAMIC=trueの設定すると、スレッドの自動調整が行われて、オーバーサブスクライブ状態を回避できます。omp_set_dynamic(1)でも可。

若干オーバーヘッドがありますが、シングルコアCPUでも動作します。

ご参考ください。

12:06 PM
2012年4月17日


yoshihingis

投稿数 54

3

菅原様、早速のご回答ありがとうございました。
 非常に貴重な知見を得られました。御礼申し上げます。

5:10 PM
2013年9月5日


trjobs

投稿数 8

4

編集長様
去年と今年夏のインテルカンファレンスに参加させて頂きまして、とても参考になる
お話を多々お聞かせ頂きまして感謝申し上げます。ちょっと間が空いてしまいましたが、
このトピックと類似した内容なもので、ここに投稿させて頂きます。もともとはGPGPUを
主に研究しておりますが、編集長のお話を拝聴し、OpenMPやMPIの適用の検討を初めて
おりまして、その中での疑問をお聞きさせて頂きたいと思います。試しましたCPUは
i5-2540Mで、2コアでHTがONでOSでは4CPUsとして動作しております。このマシンで、
よく並列計算のテストで使われる円周率の級数和計算をさせてみました。
まず、omp_get_num_procs()では当然4とでました。そして、スレッドを1, 2, 4, 8,
16, 20, 32と増やしてomp_get_wtime()で時間測定を行って比較しましたところ、
n=1: 14.9sec, n=2: 17.0sec, n=4: 13.9sec, n=8: 11.9sec, n=16: 9.3sec,
n=20: 7.7sec, n=32: 6.6sec
という結果になりました。何度か実行しましたが、ほぼこのような時間でした。
単純に考えますと、4スレッドまでは速くなりそうな気がするのですが、上の結果を
みますと、2スレッド、4スレッドの高速化はあまり顕著でなく、逆に、HT分を含めた
CPU数よりも格段に多い20,32で倍のスピードがでていることになります。これはなぜ
なのでしょうか?あと、このトピックの質問や編集長の執筆されたOpenMP並列プログラ
ミングのp.56のような実際のコア数を越えたスレッドを指示した計算の場合、各スレッド
はnum_procsを越えた通し番号がついていて、見た感じは指定しただけのスレッドが立って
いるようにはみえますが、これらはコア数で等分された数のスレッドが各コア内でタイム
シェアリングされているのでしょうか?長々とすみませんが、いろいろ調べてみましても
こうしたことについての詳しい記述が見つけられなかったものですから、ご教授頂け
ますと幸いでございます。

12:52 AM
2013年9月6日


iSUS編集部 – 菅原

投稿数 206

5

投稿は 1:05 AM – 2013年9月6日 に iSUS編集部 – 菅原 さんにより更新されました


trjobs さん

こんにちは、お問い合わせの件、ご利用の OS やコンパイラーが不明ですが次のようなことが考えられるかと思います。

実行環境は、(0,1) (2,3) の 4 つの論理コアがあり、() 内は同じ物理コアだとすると、2 スレッドの場合に (0,1) に割り当てが行われていないでしょうか?もし、インテルコンパイラーを利用されているなら、次の環境変数を設定してプログラムを実行してみていただけますか?

> set KMP_AFFINITY=verbose, scatter (windowsの場合)
> export KMP_AFFINITY=verbose, scatter (Linux bsh の場合)

これは、コンパイラーのランタイムにスレッド割り当てのトポロジーの関するる情報を表示させ、割り当てをscatter(あるスレッドの次のスレッドは、遠い論理コアに割り当てる)にすることを指示してます。これに関しては、
http://www.isus.jp/article/ope…..y-control/
を参照ください。Xeon Phi 向けの記事ですが、Coreにも適用できます。

次にスレッド数を増やした時の振る舞いですが、まずハイパースレッディングに関する記事、

1:00 AM
2013年9月6日


iSUS編集部 – 菅原

投稿数 206

6

投稿は 1:03 AM – 2013年9月6日 に iSUS編集部 – 菅原 さんにより更新されました


ああ、URLで書き込みが切れてしまいました。続きです。

http://www.isus.jp/article/per…..threading/

上記 URL をご覧ください。スレッド数が少ないと1つのスレッドの計算量は多くなり、長い時間コアに留まらなければいけません。その間OSによるコンテキスト切り替えが、何度もおこり、そのたびに別の論理コアに割り当てられる可能性があります。これはキャッシュや、パイプラインの効率を悪くするアプリケーションからは見えないオーバーヘッドです。スレッド数が多いと、1スレッドの計算量は少なくなり、コアに留まる時間が短くなるので上記の影響が少なくなる可能性もあります。

どちらにしろ、OSがHTを意識しているかが重要です。OSがHTを考慮していると、2スレッド時に (0,1) の同じ物理コアに論理スレッドを割り当てることはデフォルトでは無いと考えられます。OS等の詳細情報をお知らせください。

1:10 PM
2013年9月12日


trjobs

投稿数 8

7

投稿は 6:36 PM – 2013年9月12日 に trjobs さんにより更新されました


菅原様
早急のご回答、ありがとうございます。環境情報不足申し訳ございませんでした。環境はFreeBSD上のgcc-4.2.1でございます。近いうちにlinux+icc or ifortで試す予定なのですが、まだセットアップ中なもので、すぐ試すことのできるマルチコアの唯一の環境だったものですから。
それで、gccなものですから、ご指導頂きましたKMP_AFFINITYが使えず、調べましたところgccにはGOPM_CPU_AFFINITYなるものがあるようで試しましたら、どうもFreeBSDでは効かないようです。設定してもNUM_THREADS=2なのに4CPUで処理するようです。Linuxのsched_setaffinity()もFreeBSDではうまく使えませんでした。結局、cpusetというコマンドで4つのCPUの割り当てを指定しました。その結果、ご指導のように4論理CPUのうちの(0,1),(2,3)の2つの組み合わせが他の組み合わせより速いことがわかりました。参考までにおおよその計算時間です。逐次計算: 7.1sec、2スレッド: 12.9sec、4スレッド:9.6sec
(ただし、今回は#pragma omp forを削除してスレッドごとにループを指定してオーバーヘッドを減らして少し高速化したので4スレッドの時間が先の投稿時より速くなっています)。2coresなので倍とまではいかず、3割増しくらいの高速化になりました。ちなみに、CPUをモニタしながらCPU制御なしで2スレッドで実行しますと、どうやら4CPUで処理しているようです。計算時間はおおよそ15.6secでした。OpenMP処理するより逐次処理のほうが速かったのは計算規模が小さかったためでしょうかね。ちなみに、今回の計算規模では、同じ計算をMPIで行いますと、1桁から2桁さらに速く処理が終わるようです。これらを踏まえまして、Intel compilerでの本格的な大規模計算に移りたいと思います。
どうもありがとうございました。また、カンファレンスでのレクチャーを楽しみにお待ちしております。

12:37 PM
2013年9月17日


iSUS編集部 – 菅原

投稿数 206

8

投稿は 12:57 PM – 2013年9月17日 に iSUS編集部 – 菅原 さんにより更新されました


trjobs 様

詳細な情報ありがとうございます。HT利用時はプロセッサーの世代によっても異なりますが、pi の計算のようなメモリーアクセスが少なく、同じ演算ユニットを取りあうようなケースではHTによる並列化におけるメリットはあまり得られないかもしれません。

GCCをご利用とのことですが、並列化以前にベクトル化を適用することで、ベースラインのパフォーマンスが倍になることが期待できます。是非インテルコンパイラーを使用して、実行する環境のプロセッサーが利用できる世代のSSE/AVX命令を生成してみてください。

12:53 PM
2013年9月17日


iSUS編集部 – 菅原

投稿数 206

9

投稿は 1:01 PM – 2013年9月17日 に iSUS編集部 – 菅原 さんにより更新されました


trjobs 様

ちなみに以下のコードをインテルコンパイラーでコンパイルして、Windows環境ですが、Ivybridge と Haswell で実行した結果です。ターボモードがONなのでシングルスレッドがクロックアップで高速化されています。コンパイラーオプションは pi.c /Qopenmp のみです。インテルコンパイラーではデフォルトでSSE2命令を吐き出します。ちなみに /Qno-vec オプションでベクトル化をOFFにすると、実行時間は倍になります。ループ中で利用しているデータ型は double です。

int num_steps = 1000000000;

#pragma omp parallel for private(x) reduction(+:sum)
for (i=0; i<num_steps; i++)
{
x = (i + .5)*step;
sum += 4.0/(1.+ x*x);
}

Core i7 3770 3.4GHz HT Turbo ON
Serial 1.88 sec
HT 2 threads 0.95 sec
HT 3 threads 0.62 sec
HT 4 threads 0.59 sec
HT 6 threads 0.54 sec
HT 8 threads 0.54 sec

Core i7 4770 3.4GHz HT Turbo ON
Serial 1.82 sec
HT 2 threads 0.93 sec
HT 3 threads 0.62 sec
HT 4 threads 0.59 sec
HT 6 threads 0.53 sec
HT 8 threads 0.48 sec

2:46 PM
2013年9月30日


trjobs

投稿数 8

10

投稿は 2:57 PM – 2013年9月30日 に trjobs さんにより更新されました


菅原様
詳細なご教授とテスト計算ありがとうございました。心より感謝申し上げます。
確かにこの計算、メモリアクセスが小さいのと、利用環境がHT併用なので正確な比較評価がしずらいのかもしれません。あとはやはりgccに比べましてiccの圧倒的な速さに驚かされました。いくらi5とi7の違いはあったとしても1/10とは。。。大変詳しく教えていただきまして、ありがとうございました。

もう一つだけご教授頂けるとありがたいことがございます。
sectionsを試すのと、スレッドの動作を確認したくて下記のプログラムを走らせてみました。
今回の実行環境は、Xeon E7-8870 (1 node 10 Cores)です。
#include
#include

void sub(int *tmp, int nn){
int j,id;
id=omp_get_thread_num();
for(j=1; j<=nn; j++) *(tmp+id) +=j;
// printf(" %d, %d\n", id, *(tmp+id));
}

main(){
int i;
int S[3];

for(i=0; i<3; i++) S[i]=0;

#pragma omp parallel sections num_threads(3)
{
#pragma omp section
sub(S,10);
#pragma omp section
sub(S,50);
#pragma omp section
sub(S,100);
}

for(i=0; i<3; i++) printf("S[%d]= %d\n", i, S[i]);
}
御覧のとおり極簡単な3つの指定した数までの和を3つのsectionによるスレッドで並列処理してそれぞれをS[]に格納するというものです。合計は意地悪く小さい数字にして処理時間を極めて短くしています。
→切れそうなのでその2につづきます→

2:53 PM
2013年9月30日


trjobs

投稿数 8

11

投稿は 2:58 PM – 2013年9月30日 に trjobs さんにより更新されました


その2です。
ポイントは担当した各スレッド番号の配列S[]要素に結果を入れる点で、おそらくこのようなプログラムが必要なケースはレアでしょうが、ちょっとスレッドの動作を試したかったもので、疑問には思わないで下さい。
予定通りですと3つのスレッドが起動して各スレッドが10, 50, 100までの和をそれぞれ分担して処理して0から2までの自分のスレッド番号のS[]に結果を格納し、最後それを表示して終わりとなります。
実行結果ですが、Intelコンパイラiccで実行しますと、予定通りの動作で何度実行しても
S[0]= 55
S[1]= 1275
S[2]= 5050
となりました。問題はgccでの実行で、実行するときどきで、3、2、1スレッドで処理されてしまい、プログラム上3スレッドで処理されないと正しい合計にならないので、誤った結果になってしまいます。
以下が結果例です。
S[0]= 1275
S[1]= 5105
S[2]= 0

S[0]= 5050
S[1]= 1330
S[2]= 0

S[0]= 0
S[1]= 6380
S[2]= 0
上記の結果からiccは必ず指定したスレッド数で処理され、gccは必ずしも指定したスレッド数で処理されるとは限らず、OS任せということなのでしょうか?試しに、
setenv GOMP_CPU_AFFINITY "0,1,2"も試しましたが、設定なしよりも3スレッド処理の割合が増えますが、やはり2スレッド処理の場合もあります。

さらに、関数sub中のprintfのコメントアウトをはずすと3スレッドで処理され正しい結果となるようですが、これは単にprintfが処理時間を食って最初のスレッドが3つ目sectionブロックの処理前に処理が終わらなくなっただけなのでしょうか?ただし、2 coresのMacで試すと1スレッド処理されたりすることもあるようです(もちろん3スレッド指定なのでオーバサブスクライブになっているのであまり参考にはならないでしょうが。。。)

上記は、意地悪く処理時間が極めて短く、並列する意味のないプログラムなのですが、sections並列の挙動をいろいろ知りたかったものですから試してみました。
ご教授頂けますと幸いでございます。

12:58 AM
2013年10月1日


iSUS編集部 – 菅原

投稿数 206

12

trjobs 様

ちょっと調べてみますね。
ちなみに GCC のバージョンと OS は何でしょうか?

7:52 AM
2013年10月1日


trjobs

投稿数 8

13

投稿は 9:05 AM – 2013年10月1日 に trjobs さんにより更新されました


菅原様
早速のご返答、ありがとうございます。
書き忘れ、すみませんでした。OSは
Linux version 2.6.18-238.9.1.el5
で、gccは
gcc version 4.1.2 20080704 (Red Hat 4.1.2-50)
ですね。OpenMPのバージョンは
200505 (gcc)
200805 (icc)
です。すみません、実行する前にチェックすべきことでした。ちょっと前にリプレイスした計算機センターのサーバなもので、こんな古いバージョンだと思っていませんでした。菅原様の執筆された本によりますと、4.1はプレビューサポートなようですね。これが原因?と思ったのですが、上のメイルにありますように2 coresのMacでも試しておりまして、こちらのバージョンはgcc 4.2.1ですので、OpenMPのバージョンは同じですが、プレビューサポートだからということは関係ないようです。あと、コンパイラオプションは-(f)openmpだけで行っております。

9:23 PM
2013年10月1日


iSUS編集部 – 菅原

投稿数 206

14

投稿は 9:37 PM – 2013年10月1日 に iSUS編集部 – 菅原 さんにより更新されました


trjobs 様

GCCで生成したコードは、最初のセクション領域が終了してしまって、以降のセクション領域に同じスレッドが割り当てられることがあるようですね。実装としては、誤りではないのですが、いささか…

原因としては、section 構文の実装の仕方にあります。GCCでは、セクション構文が各セクション領域への分岐と戻りで構成され、セクションの生成とその呼び出しがシーケンシャルにコード中に出てきます。.s を出してみていただくと、納得いけるかと。そのため、処理を少し重くするか、各セクション領域に sleep を挿入してみると、意図した動作になります。

インテルコンパイラーでは、意図通り動作しているということですが、上記のような理由から、今後も同じであるという保証はありません。スレッド数や特定のスレッドIDに依存したプログラミングはされなことをお勧めします。以下参照ください。

http://www.isus.jp/article/ope…..nmp-traps/

検証したGCCは、4.4.5 です。

3:08 PM
2013年10月19日


trjobs

投稿数 8

15

菅原様
迅速なご回答を頂いたにも関わらずお返事遅れて申し訳ございませんでした。

なるほど、セクションはシーケンシャルに生成されるんですね。そうすると動作が
納得できますね。あと、OpenMPではsection並列の実装はそのような規定だったの
ですね。つまり、設定したスレッド数で必ずしも並列処理する必要はないということ
でしょうか。いずれにしても、並列処理は処理時間のかかるjobを実行しますから
実装上そうゆう規定はないにしても通常NUM_THREADSで指定したスレッド数で
処理されるのでしょうね。なるほどiccでも実は必ずしも指定したスレッド数で
section並列が処理される実装ではなかったのですねぇ。。。

今回試したかったことの動作の確証とその裏がよくわかりました。お聞きしてよかっ
たです。どうもありがとうございました。
心より感謝申し上げます。