自動並列化並びにOpenMP 並列化を行うためのオプション

対象 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 
  • -Mconcur オプションは、プログラム内のループレベルの並列性を抽出し、自動並列化を行うためのオプションです。concur=フラグ なしで使用しても構いません。
  • -Mconcur=dist:{block|cyclic} のサブフラグを使用して、並列分割実行のブロック分け制御が可能です。block がデフォルトです。block は並列計算範囲を等分に分ける方法であり、cyclic はラウンドロビン形式に、例えば、cpu 0 が 0, 3, 6, .. を cpu 1 が 1, 4, 7,...を cpu 2 が 2, 5, 8,..を受け持つような計算分割を行います。
  • -Mconcur=cncall は、ループ内に関数あるいはサブルーチンが含まれている場合の並列化を行うことは安全であることをコンパイラに指示します。このフラグが指定しないデフォルトは、ループ内に関数あるいはサブルーチンが含まれている場合の並列化を行いません。
  • -Mconcur=[no]innermost は、最内側ループの自動並列化を許可する(しない)オプションです。これは、粒度の小さなものを並列化の対象にするため、この適用は性能の効果を確かめてからお使いください。デフォルトは、最内側ループの自動並列化を許可しません。このオプションは、PGI 6.1 から適用されました。
  • -Mconcur=allcores サブオプション (PGI 8.0 以降)は、実行時の環境変数 OMP_NUM_THREADS あるいは NCPUS がセットされていない場合に、すべての有効なコアを使用するようにする。 (リンク時に指定すること) このフラグはNUMA構成のシステムの場合は、付けておいた方が無難でしょう。
  • -Mconcur=bind サブオプション (PGI 8.0 以降 Linux 版のみ)は、スレッドを物理的なコアにバインドして固定した状態で実行する。 (リンク時に指定すること)
  • -Mconcur=altcode は、並列化ループに対して、並列コードだけではなく、並列ではないスカラコードも同時に生成するように指示します。altcode のみの指定の場合、パラレライザは適切なカットオフ値(ループ長がその値より大きい場合に並列ルールを実行し、それよりも小さい場合にスカラコードを実行する、その閾値を言う)を設定します。また、altcode:n と指定した場合、ループ長が n 以上のとき並列コードを実行するようなプログラムが生成されます。-Mconcur=noaltcode は代替スカラコードを生成しません。
  • -Mconcur=altreduction[:n] は、リダクション演算を含む並列ループに対して、並列コードだけではなく、スカラコードも同時に生成するように指示します。並列化ループにリダクションが含まれていた場合、ループ長が n 以下の場合のみ、代替スカラコードも生成します。
  • -Mconcur=[no]assoc は、リダクションを伴うループの並列化を有効化/無効化します。リダクションとは、S = S + {operations} 形式の演算を言います。
  • -Mconcur=[no]numa は、システムのNUMAライブラリ(libnuma) を明示的にリンクする/させないためのオプションです。
  • -Mconcur=level:n は多重ルール構成の場合の並列化対象の深さ(レベル)を指定するものです。デフォルトは n=3 です。
  • -Mconcur オプションは、コンパイル時だけではなく、リンク時のオプションとしても必要です。特に、Makfile を使用している場合はご注意ください。つけなければ、リンク時のエラーとなります。
  • 上記のオプションは、自動並列化のオプションのみ例示しています。実際には、その他の最適化オプションも添えて、コンパイルしてください。

情報

並列化プログラムを実行するには、以下の環境変数を予めセットしてから実行してください。以下は、4スレッド並列の実行を指示するものです。

setenv OMP_NUM_THREADS 4 (csh 系)
export OMP_NUM_THREADS=4 (bash系)
あるいは、
setenv NCPUS 4 (csh 系)
export NCPUS=4 (bash系)

【関連情報】
「PGI コンパイラによる並列処理での NPTL スレッドライブラリについて」

■ OpenMPディレクティブを含むプログラムに対してコンパイルを行う際のオプション

 pgfortran -mp[=align,numa,allcores] -fastsse -Minfo test.f 
  • -mp オプションは、共有メモリ型プログラミングモデルである OpenMP ディレクティブを用いた 並列プログラムを並列コンパイルする場合に使用します。このオプションは、コンパイル時だけではなく、リンク時にも必要です
  • -mp=align サブオプションは、並列化と SSE によるベクトル化の両方が適用されるループにおいて、ベクトル化のためのアライメント(整列)を最大化するようなアルゴリズムを使用して、OpenMP スレッドにループ回数を割り当てるようにするものです。この機能は、このような特性を帯びた多くのループがプログラムに存在する時に性能が向上します。しかし、一方、各ループの中で、非常に大きなタスク・ワークを含むループで、そのループ長が相対的に短いプログラムにおいては、結果としてロードバランスの問題が生じて大きく性能を落とす場合がありますので注意が必要です。このオプションを適用し性能を確認してから使用してください。(この align サブオプションは、PGI 6.1 以降のオプションです)
  • -mp=numa サブオプションは、Linux/Windows が提供している NUMA ライブラリ(libnuma.so) を一般的なスレッドライブラリの代わりにリンクする機能を有する。また、それに伴い、コンパイラの最適化機能を改善している。 UMA アーキテクチャのLinux/Windowsシステムでは、メモリアクセスと OpenMP 計算がより効率的に行えます。
    PGI 6.1 以降においては、-mp=numa の numa サブオプションは使用しなくても自動的にセットされます。PGIコンパイラをインストールした時点で、libnuma ライブラリを備えたシステムであれば、-Mconcur あるいは-mp の使用時に自動的にリンクできるようにコンパイラ・システム内部で設定されます。なお、libnumaをリンクしたくない場合は、-Mconcur=nonuma あるいは-mp=nonuma を設定してください。
    PGI 6.2 以降においては、システムに libnuma ライブラリが存在しない場合、PGI コンパイラは、そのための stub(仲介)ライブラリを提供します。
  • -mp=allcores サブオプション (PGI 8.0 以降)は、実行時の環境変数 OMP_NUM_THREADS あるいは NCPUS がセットされていない場合に、すべての有効なコアを使用するようにする。(リンク時にも指定すること)このフラグはNUMA構成のシステムの場合は、付けておいた方が無難でしょう。
  • -mp=bind サブオプション (PGI 8.0 以降 Linux版のみ)は、スレッドを物理的なコアにバインドして固定した状態で実行する。(リンク時にも指定すること)
    (ご参考) NUMAアーキテクチャ上でのスレッド制御を行うためのマルチプロセッサ環境変数

情報

性能最適化のオプションを使い分ける際は、必ず、それぞれのオプションを指定して、性能を評価してください。PGI に限らず、コンパイラ共通の特性により、場合によっては遅くなる場合もあるため、一番最速なオプションを評価し使用して下さい。

■ OpenMP/自動並列 実行時に設定する環境変数

【注意】インテルのプロセッサでは、「Hyperthreading」 と言う機能を有しています。物理的なプロセッサ・コア数の 2 倍の並列スレッド実行を行えるようにした機能ですが、HPC 並列実行環境では、BIOS レベルで、この機能を disable にしていただいた方が良いかもしれません。この「Hyperthreading」によって、物理コアの数を越えた並列度の加速性が常に得られる訳ではありません。Hyperthreadingを ON (これがデフォルトの状態です)の場合、性能が低下するケースも多く見られます。特に、メモリバウンドな特性を有するアプリケーションでは、性能は逆に劣化します。「Hyperthreading」機構は、各コアに、そのレジスタ群を 2 セット用意しているものであり、演算器セットを 2 セット用意しているものではありません。従って、各コア当たり、必ず演算性能が 2 倍になる構造とはなっていません。以下の機能で述べる、並列度数の指定では、物理コア数による指定を行っていただいた方が無難です。例えば、Intel Sandybridge プロセッサで、4 コア(ハイパースレッドで 8 論理コアのプロセッサ)の場合の並列度の指定では、OMP_NUM_THREADS=4 がその最大値の指定となります。

  • OMP_NUM_THREADS 環境変数は、並列実行で使用するスレッド数を指定するものです。
  • OMP_SCHEDULE 環境変数は、DO あるいは PARALLEL DO ワークシェアリング並列実行時の分割方法のスケジューリング方法を定義します。デフォルトは、PGI の STATIC かつチャンクサイズは 1 です。以下は、設定方法の一例です。なお、この環境変数は OpenMP 並列性能を大きく変化させるものです。デフォルトは STATIC スケジューリングですが、これで性能が思わしくない場合、DYNAMIC 等をお試しください。この場合のプログラム上の OMP directive は、!$OMP DO schedule(runtime)であることが望ましいです。(CPUとメモリとの間のチャネル数にも依存して、並列性能は大きく変わります。使用スレッド数を物理的なコア数よりも小さく設定したほうが速い場合も往々にしてあります)

     $ setenv OMP_SCHEDULE “STATIC, 5”
     $ setenv OMP_SCHEDULE “GUIDED, 8”
     $ setenv OMP_SCHEDULE “DYNAMIC”
  • OMP_NESTED(ネスト)は、以下の条件において有効となります(PGI 2012以降)。デフォルトでは、OMP_NESTED は無効となっております。
    1. 環境変数 OMP_NESTED=TRUE とOMP_MAX_ACTIVE_LEVELS=n の二つを必ず指定すること。Fortran の API を利用する場合は、ネスト構造の到達前に以下のような指定が必要です。
      --- 環境変数での指定の場合 ---
      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) ! (一例)
      
    2. orphaned nested parallelism の構成の場合に、ネスト実行が有効となります。例えば、workshare 実行する部分が orphaned な別のルーチン配下で並列実行するような形を言います(parallel region配下で call したルーチン内で omp do を行う形態)。要は、parallel directive が「別の」ルーチン(親ルーチン)で指定されている必要があります。
      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
  • MPSTKZ 環境変数は並列リージョンの各スレッド・ローカルのスタックサイズを増加させるために使用します。setenv MPSTKZ 8M 等の方法で設定し、M は MB を意味します。Linux のデフォルトサイズは、多くの場合 2MB に設定されております。並列実行時に、セグメンテーション・フォールト等の問題が出た場合は、まず、このサイズを増加させてください。これでも問題が解決されなければ、Linux上のハード・リミットが設定されている可能性があります。以下の記事「自動並列・OpenMP 並列実行時の Segmentation fault の対処法」をご参考にしてください。
  • OMP_WAIT_POLICY (Proposed OpenMP 3.0 Feature) 環境変数が、PGI 7.0 から導入されました。この環境変数値は並列スレッドのアイドルの方法を指定するもので、特に、アイドル時、spin (busy wait) か sleep のどちらを使用するかを指定するものです。値はACTIVEあるいはPASSIVEとなります。この挙動は、自動並列化によって生成されるスレッドに対しても適用されます。並列実行時のスレッドがアイドルする局面は、バリア同期のとき、あるいはクリティカル・リージョンに入ったとき、あるいは、並列リージョン間のシリアル動作の場合等があります。この中でバリア同期は、常にMP_SPIN 環境変数によって指定される間隔で sched_yield システムコールによる busy wait を使用します。シリアルリージョンの実行中、待機しているスレッドは、バリア(ACTIVE)を使用した busy wait の状態か、あるいはオペレーティングシステム内の mutex(PASSIVE) を使用した politely wait のどちらかを選択でき、これは、OMP_WAIT_POLICY をセットすることにより可能となります。デフォルトは、ACTIVE です。
    ACTIVE をセットした時は、アイドルしているスレッド (busy wait) は 100% CPU を使用します。このメカニズムは、頻繁に並列リージョンに入ったり、出たりするような並列プログラムにとっては、並列リージョンに入る際、速い動作が可能となるため並列オーバーヘッドが小さくなる利点があります。一方、PASSIVE の場合は、次の並列リージョンに入るまで(スレッドが再起動されるまで)、CPU 時間を消費しない、オペレーティングシステム内の mutex上での wait 動作となります。これは、シリアルリージョンが比較的長い場合とか、マルチユーザ環境で CPU リソースを共有しているような場合に設定します。
  • OMP_STACKSIZE (Proposed OpenMP 3.0 Feature) 環境変数とスタックサイズ API が、PGI 7.0 より新たに生成されるスタック(子スタック)のサイズを制御するためにサポートされました。API の関数としては、omp_set_stack_size と omp_get_stack_size が実装されております。この環境変数は、新たにスレッドが生成される度にこの値が反映され、システムのデフォルト値を上書きします。数字の後にB,K,M,Gのサイズ識別子を付加することができます。B=Byte,K=Kbyte,M=Megabyte,G=Gigabayte の意味です。なお、上記の新しい API 関数(omp_set_stack_size と omp_get_stack_size)については、PGIのローカルな API 関数であり、OpenMP準拠した関数ではないため、ポータビリティを重視するプログラムでは使用しない方が良いです。
  • MP_BINDは、NUMA マルチプロセッサ環境変数です。MP_BIND、MP_BLIST、MP_SPIN環境変数を提供しています。

■ 並列性能に効果が期待されるコンパイル・オプション

 pgfortran {-mp|-Mconcur} -Munsafe_par_align -Mnontemporal -Mmovnt
  • -M[no]unsafe_par_align オプションは、並列化ループでの配列の参照において、その配列の最初の要素が「整列」されている場合、「整列移動(aligned moves)」を行うことが安全であるとみなします。 NOTE: 但し、このオプションは、コンパイラがその安全性を疑った場合でも、「整列移動」で行うコードを生成しますので、その計算結果の検証は必ず行ってください。このオプションは、特に、 STREAM Benchmark やメモリ・インテンシブなメモリアクセスを含むループブロックの並列化で効果を発揮しますが、標準利用のオプションとして推奨するものではありません。
  • -Mnontemporal オプションは、non-temporal ストア並びにプリフェッチの生成を強制するオプションです。 -fastsse と共に使用します。性能が向上する場合があります。
  • -M[no]movnt オプションは、これまで使用してきた-Mnontemporal を置き換えるものです。  (PGI 6.1以降)

■ プログラムの自動並列化コンパイル例と実行例

(コンパイル)
[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

■ 自動並列・OpenMP 並列実行時の Segmentation fault の対処法

この 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 をしていただくことになります。プログラムのスレッド使用量は、外側から見定めることができません。