PGI 自動並列化 OpenMP スレッド並列 オプション
PGI の F77, F2003, C, C++ のコンパイラを使用して、マルチコア・プロセッサ上で並列化を行うためのコンパイル・オプションについて説明します。以下は、主に、pgfortran を使用した場合の例ですが、コンパイラのオプションの設定方法は、他の言語コンパイラでも同じです。また、スレッド並列実行を行うための環境変数等に関しても説明します。
2012年2月2日更新 Copyright © 株式会社ソフテック
pgfortran -fastsse -Mconcur -Minfo test.f pgcc/pgc++ -fastsse -Mconcur -Minfo source.c
並列化プログラムを実行するには、以下の環境変数を予めセットしてから実行してください。以下は、4スレッド並列の実行を指示するものです。
setenv OMP_NUM_THREADS 4 (csh 系)
export OMP_NUM_THREADS=4 (bash系)
あるいは、
setenv NCPUS 4 (csh 系)
export NCPUS=4 (bash系)
【関連情報】
「PGI コンパイラによる並列処理での NPTL スレッドライブラリについて」
pgfortran -mp[=align,numa,allcores] -fastsse -Minfo test.f
性能最適化のオプションを使い分ける際は、必ず、それぞれのオプションを指定して、性能を評価してください。PGI に限らず、コンパイラ共通の特性により、場合によっては遅くなる場合もあるため、一番最速なオプションを評価し使用して下さい。
【注意】インテルのプロセッサでは、「Hyperthreading」 と言う機能を有しています。物理的なプロセッサ・コア数の 2 倍の並列スレッド実行を行えるようにした機能ですが、HPC 並列実行環境では、BIOS レベルで、この機能を disable にしていただいた方が良いかもしれません。この「Hyperthreading」によって、物理コアの数を越えた並列度の加速性が常に得られる訳ではありません。Hyperthreadingを ON (これがデフォルトの状態です)の場合、性能が低下するケースも多く見られます。特に、メモリバウンドな特性を有するアプリケーションでは、性能は逆に劣化します。「Hyperthreading」機構は、各コアに、そのレジスタ群を 2 セット用意しているものであり、演算器セットを 2 セット用意しているものではありません。従って、各コア当たり、必ず演算性能が 2 倍になる構造とはなっていません。以下の機能で述べる、並列度数の指定では、物理コア数による指定を行っていただいた方が無難です。例えば、Intel Sandybridge プロセッサで、4 コア(ハイパースレッドで 8 論理コアのプロセッサ)の場合の並列度の指定では、OMP_NUM_THREADS=4 がその最大値の指定となります。
--- 環境変数での指定の場合 --- export OMP_NESTED=TRUE export OMP_MAX_ACTIVE_LEVELS=2 --- API を使用 --- use omp_lib call omp_set_nested(.true.) call omp_set_max_active_levels(2) ! (一例)
subroutine foo() use omp_lib !$OMP PARALLEL NUM_THREADS(6) print *, "Hi from foo ", OMP_get_thread_num() !$OMP END PARALLEL end subroutine foo program test1 use omp_lib call OMP_set_num_threads(12) call OMP_set_nested(.true.) call OMP_set_max_active_levels(2) print *, omp_get_max_active_levels() print *, OMP_get_nested() !$OMP PARALLEL NUM_THREADS(2) print *, "Hello", OMP_get_thread_num() call foo() ! orphaned routine --> nested parallel !$OMP PARALLEL NUM_THREADS(4) print *, "Hi ", OMP_get_thread_num() ! No nested using PGI compiler !$OMP END PARALLEL !$OMP END PARALLEL end program test1 $ pgf90 -mp -Minfo nest.f90 foo: 3, Parallel region activated 4, Parallel region terminated test1: 18, Parallel region activated 23, Parallel region activated 24, Parallel region terminated $ a.out 2 T Hello 0 Hello 1 Hi from foo 0 ここは nest される Hi from foo 2 Hi from foo 3 Hi from foo 4 Hi from foo 1 Hi from foo 5 Hi from foo 0 Hi from foo 2 Hi from foo 1 Hi from foo 4 Hi from foo 5 Hi from foo 3 Hi 0 これは nestされない Hi 0
pgfortran {-mp|-Mconcur} -Munsafe_par_align -Mnontemporal -Mmovnt
(コンパイル) [kato@photon30 Himeno]$ pgfortran -fastsse -Minfo -Mconcur himenoBMTxp_omp.f90 initmt: 235, Memory zero idiom, loop replaced by call to __c_mzero4 Loop not parallelized: may not be beneficial 236, Memory zero idiom, loop replaced by call to __c_mzero4 Loop not parallelized: may not be beneficial 237, Memory zero idiom, loop replaced by call to __c_mzero4 Loop not parallelized: may not be beneficial 238, Memory zero idiom, loop replaced by call to __c_mzero4 Loop not parallelized: innermost 239, Memory zero idiom, loop replaced by call to __c_mzero4 Loop not parallelized: innermost 240, Memory zero idiom, loop replaced by call to __c_mzero4 Loop not parallelized: innermost 241, Memory zero idiom, loop replaced by call to __c_mzero4 Loop not parallelized: innermost 243, Memory set idiom, loop replaced by call to __c_mset4 Loop not vectorized/parallelized: loop count too small Parallel code generated with block distribution if trip count is greater than or equal to 50 244, Parallel code generated with block distribution if trip count is greater than or equal to 33 Generated 3 alternate versions of the loop Generated vector sse code for the loop 245, Memory set idiom, loop replaced by call to __c_mset4 Parallel code generated with block distribution if trip count is greater than or equal to 33 248, Memory set idiom, loop replaced by call to __c_mset4 Loop not parallelized: innermost jacobi: 296, Loop not vectorized/parallelized: too deeply nested 303, Parallel code generated with block distribution if trip count is greater than or equal to 33 305, Generated 3 alternate versions of the loop Generated vector sse code for the loop Generated 8 prefetch instructions for the loop 327, Parallel code generated with block distribution if trip count is greater than or equal to 50 328, Memory copy idiom, loop replaced by call to __c_mcopy4 (環境変数の設定=スレッド数) [kato@photon30 Himeno]$ export OMP_NUM_THREADS=2 (実行) kato@photon30 Himeno]$ time ./a.out For example: Grid-size= XS (64x32x32) S (128x64x64) M (256x128x128) L (512x256x256) XL (1024x512x512) Grid-size = m mimax= 257 mjmax= 129 mkmax= 129 imax= 256 jmax= 128 kmax= 128 Time measurement accuracy : .10000E-05 Start rehearsal measurement process. Measure the performance in 3 times. MFLOPS: 7940.452 time(s): 5.1799999999999999E-002 1.6923338E-03 Now, start the actual measurement process. The loop will be excuted in 3474 times. This will take about one minute. Wait for a while. Loop executed for 3474 times Gosa : 2.7195489E-04 MFLOPS: 8030.604 time(s): 59.31100600000000 Score based on Pentium III 600MHz : 96.94115 real 1m5.932s user 2m3.787s sys 0m0.024s
この Segmentation fault の問題は、自動並列化あるいは OpenMP 並列化を行った際のスレッド実行時の問題です。この問題の解決を図るために、最初に、上述した MPSTKZ 環境変数、あるいは OMP_STACKSIZE 環境変数の変更を行って、再度実行してみて下さい。それでも、同様なエラーが生じる場合は、以下で述べる、OS 上のリミット値の変更を行って下さい。
(ご参考)for Windows/Linux版
コンパイル時に -Mchkstk オプションを付けて実行モジュールを作成し、環境変数 PGI_STACK_USAGE に対して任意の数字をセットしますと、スレッド実行プログラムの終了時に、以下のようなメッセージが出ます。プログラムのスレッドが使用したスタックサイズ (used) が出力されますので、これを参考に、スタックサイズの調整を行っても良いでしょう。
thread 0 stack: max 10228KB, used 2KB
共有メモリ上の複数の CPU を使用した並列実行では、実行時に並列プロセス上に存在する「スタック領域」を利用します。このスタック領域は、各 OS のビルド時に default として設定されておりますが、このデフォルトの stack size の領域を上回って使用される場合、このエラーが生じます。即ち、実行プログラムの配列サイズが大きくなった場合に、実行プロセスのスタック・サイズも比例して大きなものを利用しますので、これを超えたためのエラーです。
Linux/OS X 上での対処法としては、ログインしたシェル環境の中で、stack size を明示的に大きく指定する方法をとらなければなりません。これは、PGI の制約ではなく、一般的なスレッド方式の並列処理上で付きまとう、よく知られた問題です。以下の方法でそのサイズを変更してください。また、Windowsの場合は、こちらのページにてスタック・サイズの変更方法を説明しています。
ログイン・シェルが Bシェル系(sh,bsh)の場合とCシェル(csh,tcsh)系の場合とでコマンドが異なります。
Bシェル系の場合のサイズを変更する build-in command は ulimit です。
Cシェル系の場合のサイズを変更する build-in command は limit です。
現在の stacksize に限らず、プロセスのパラメータのサイズを見るには、 【Bシェル系】 $ ulimit -a core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 62692 max locked memory (kbytes, -l) 64 max memory size (kbytes, -m) unlimited open files (-n) 1024 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) 10240 cpu time (seconds, -t) unlimited max user processes (-u) 1024 virtual memory (kbytes, -v) unlimited file locks (-x) unlimited スタックサイズのものだけを見たい場合、 $ ulimit -s unlimited 【Cシェル系】 $ limit cputime unlimited filesize unlimited datasize unlimited stacksize 10240 kbytes coredumpsize 0 kbytes memoryuse unlimited vmemoryuse unlimited descriptors 1024 memorylocked 64 kbytes maxproc 1024 スタックサイズのものだけを見たい場合、 $ limit stacksize stacksize unlimited
となります。もし、デフォルトで、stacksize が unlimited となっている場合、この unlimited と言うのは無制限と言う意味ではありますが、実際は、これは無制限と言う振る舞いをしないため、明示的な数字で指定する必要があります(特に、sh 系の場合)。
従って、最大上限スタックサイズを陽的な数字で指定してください。以下の例では、16384 KByte を指定する場合の例です。指定する数字の単位は、KByte
です。また、1024 の倍数にしてください。
【Bシェル系】
$ ulimit -s 16384
確認する
$ ulimit -s
16384
(注意)Bシェル系の場合、一度指定したら、このシェル環境上ではこの上限サイズは変更できません。1回切りです。たとえ変更しようとしても、エラーメッセージが表示されます。この場合は、一度、ログアウトして再度、ログインして同じような手続きを行ってください。
【Cシェル系】
$ limit stacksize 16384
確認する
$ limit stacksize
stacksize 16384 kbytes
(注意) Cシェル系の場合は、何度でも指定を変更できます。
以上のような形で、シェルのスタックサイズの上限を適宜指定してください。どの値が適切かは、何度か try & error をしていただくことになります。プログラムのスレッド使用量は、外側から見定めることができません。