NUMA マルチプロセッサ環境変数について

対象 NUMA OpenMP MPbind PGIコンパイラ

 PGI コンパイラは、AMD 社の Opteron やインテル社の Nehalem 以降の NUMA アーキテクチャ上での OpenMP スレッド並列を制御するための環境変数を提供します。これをマルチプロセッサ環境変数と言い、特にデュアルコア・プロセッサを含むシステムのスレッド並列制御に有効です。ここでは、この環境変数の機能について紹介します。なお、PGI コンパイラは、AMD64/intel®64 のプロセッサのマイクロ・アーキテクチャに応じた最適化だけでなく、UMA/NUMA アーキテクチャにも「最適化」する機能を有しています。(2005年11月24日 初稿、2011年8月修正)

NUMA用オプション、環境変数について

-mp コンパイルオプション

 コンパイラオプションとして PGI 6.0-5 より、-mp=numa (numa サブオプション)が追加されております。これは、NUMA アーキテクチャを採用する AMD並びにインテルのマルチ(コア)プロセッサシステム用のオプションです。インテルRのプロセッサシステムでは、Hehalem アーキテクチャ以降で NUMA を採用しています。
 -mp オプションは、OpenMP の指示行を含むプログラムの並列処理モジュールを生成するオプションですが、その性能の向上のために、NUMA システムライブラリをリンクし、process(thread)-to-CPU スケジューリングの CPU affinity (親和度)等の機能を有効にするオプションとなります。NUMA アーキテクチャのシステムにおいて有効であり、現在、Linux やWindows の OS 下でこの NUMA ライブラリをリンクすることが可能です。これによって、NUMAアーキテクチャに対するユーザ側での制御自由度が向上されております。

マルチコア上でのスレッド制御を行うためのマルチプロセッサ(mp)用環境変数

 マルチコア環境でのスレッド制御を行うためのマルチプロセッサ(mp) 環境変数(MP_BIND、MP_BLIST、MP_SPIN) が PGI 6.0-5 から追加されました。これらの環境変数は、スレッドの実行を別の CPU に移行(切り替え)させるかどうかの設定 (MP_BIND)、スレッドを物理的な CPU にバインドして固定させるための設定 (MP_BLIST)、バリア同期の待ち状態に入ったと時に、アイドル・スレッドがどの程度、プロセス・サイクルを消費するかを制御する設定 (MP_SPIN) を行うものです。

  • MP_BIND環境変数 - OpenMP スレッドを物理 CPU にバインド(結合)するために、MP_BIND環境変数を導入しました。この環境変数を yes あるいは y とセットするとこの機能が有効となり、no あるいは n をセットするとこのバインド機能は無効となります。デフォルトはnoです。これは実行時に OpenMP ランタイム・サポート・ライブラリによって解釈され、PGI コンパイラの挙動に対して影響を及ぼすものではありません。この機能は、NUMA 対応の Linux や Micorosoft Windows の OS 下で、かつ、-mp=numa オプションをつけてコンパイルされたモジュールに対して有効です。最新の Intel Sandybridge プロセッサ上の Windows OS は、NUMA 対応となっていますので、この環境変数は有効です。特に、メモリ・バウンドな特性を有するプログラムでは、OpenMP等による並列実行を行う際に、MP_BIND の指定とMP_BLISTを変更することによって性能が大きく変わることがあります。最初は、下記の MP_BLIST を指定しないで、MP_BIND を yes として設定してから、性能を確かめて下さい。MP_BIND=y を設定すると、OS が物理的なCPUコアに、実行スレッドを 1対1にバインド します。
  • MP_BLIST環境変 - MP_BLIST 環境変数を新たに追加しました。スレッドと CPU の固定を定義するために使用され、例えば、MP_BLIST=3,2,1,0 の指定では、CPU 3,2,1,0 がそれぞれスレッド 0,1,2,3 にマッピングされます。-mp=numa オプションをつけてコンパイルされたモジュールに対して有効です。この機能は、NUMA 対応の Linux や Micorosoft Windows の OS 下で、かつ、-mp=numa オプションをつけてコンパイルされたモジュールに対して有効です。PGI 6.1 以降では、、-mp=numa オプションは必要ありません。OSが NUMA 対応カーネルの場合、コンパイル時に、自動的にこのオプションは付加されます。
  • MP_SPIN環境変数 - MP_SPIN 環境変数を新たに追加しました(6.0-5) 。並列リージョンで実行しているスレッドが、バリア同期に入った場合、セマフォ上でのスピン実行を行います。MP_SPIN は、sched_yield() システムコールが呼び出されるされる前に、セマフォを確認する時間(サイクル数)を指定するために使われます。sched_yield() システムコールは、他のプロセスに実行を許すために、スレッドを再スケジューリングするためのものですが、並列処理においては、他にスケジューリングされない方が望ましいですので、この環境変数でその時間を設定します。デフォルトは、1000000 (Linux)サイクルです。もし、MP_SPIN=-1 とセットした場合、セマフォ上で、sched_yield() コールせずに、スピンし全スレッドが同期するまで待ちます。

NUMAアーキテクチャでの実行スレッド制御

 Intel Nahalem や AMD Opteron システムのような共有メモリ型に属する NUMA アーキテクチャの構成を簡単に言うと、プロセッサそれぞれに直結したメモリシステムを持つ構成で、さらにプロセッサ間を HyperTransport と言う高速な通信路で直結してマルチ・プロセッサシステムを構成したものと考えればよい。各プロセッサに直結したメモリは、ノード内の全プロセッサからアクセス可能であり、この意味で共有メモリ型として機能します。しかし、当然ながら、プロセッサ自身に直結したメモリアクセス(ローカル・アクセス)の性能と他のプロセッサに直結したメモリをアクセス(リモート・アクセス)する性能とは異なり、リモートアクセスの方が多少性能が劣化します。とは言え、MUMA は、従来のバス型構成でメモリにアクセスする方法に比べ、メモリ帯域のスケーラビリティが良いという特長を持ちます。

 プログラムを実行する際、実行モジュールをメモリにロードし、プログラムの配列データ等をアロケートすることは NUMA カーネルの役目ですが、これとは別にユーザレベルで各並列スレッドがどの物理的な CPU コアで実行させるかを制御できると、実行性能の最適化の自由度が増します。一般的には、カーネルが指定する CPU(Core) 割り当て、メモリインタリーブ方法をそのまま使用しても何ら問題ないですが、例えば、プログラムでの論理スレッド 0 のタスクを物理的な CPU 0 に常に割り当てて計算したいと言うこともあります。また、Linux カーネルのデフォルトは、非常に短い時間で CPU 割り当てスケジューリングを行うように設定されており、プログラム実行中、論理スレッドが各 CPU に渡り歩くと言った現象が見られることがあります。HPC の計算のように、最初からスレッドタスク 0 は CPU 0、スレッド 1 は、CPU 1 にと言った固定して使えるようにすれば、CPU スケジューリングのオーバヘッドの軽減や、 NUMA であればプロセッサ自身のローカルメモリに直アクセスする機会が増えると言った低オーバヘッドの計算ができます。スレッド(プロセス)を固定した CPU に固定させるような機能を process-to-CPU(core) スケジューリングにおける affinity と言うが、この機能がまさに上述した機能と言えます。また、特に マルチ・プロセッサの場合、性能を考慮して二つのCPUコアのうち、常に一つだけ使用すると言ったことにおいても、CPUバインド機能は有効である。PGI 6.0-5 以降のバージョンでは、この CPU バインドする機能を使用できる。

MP_BINDとMP_BLISTの使用例

 この機能を使用するためには、以下のことを行う必要があります。

  • OpenMP 並列を行うためのコンパイラオプションで、-mp=numa を付加してコンパイル・リンクすること。なお、PGI 6.1 以降では、コンパイラをインストールしたシステム上に、/usr/lib64/libnuma.so ファイルが存在していた場合、コンパイラの起動初期設定ファイル上に -mp=numa を設定するため、明示的な -mp=numa オプションの明示的な指定は必要ありません。
  • NUMAマルチプロセッサ環境変数(MP_BIND、MP_BLIST) をセットして実行する。(上記の環境変数の説明を参照)

 上記の設定を行った後、その OpenMP 並列実行で CPU を自由にバインドできる状況を以下に示します。これは、過去の PGI 6.x バージョンを利用して行った例です。ここでは、実験機材がデュアルコアの 2 CPUコアのシステムで簡単な例を示します。こうした使い方は、実際には 2way, 4way 等のシステムの場合、有効な活用ができます。

【コンパイル オプション】 

    pgf95 -fastsse -Minfo -mp=numa xx.f  (-mp は OpenMP 並列を行うオプション)

【環境変数の設定】

    export MP_BIND=yes    (OpenMPスレッドを物理 CPU(core) にバインドすることを宣言)
    export OMP_NUM_THREADS=2 (スレッド数を2個使用することを宣言)
  この後、環境変数を変えて実行する

プロセッサの動きを xosview で観察する。左図は、並列実行前の状況。グリーンがアイドル状態を示す。CPU0、CPU1 は 0% あるいはカーネルで使用されている使用率が表示されている。

● MP_BLIST=0,1を設定する

次に、MP_BLIST 変数でCPUバインドの方法を指定する。 並列スレッド 0 を CPU0、 並列スレッド 1 を CPU1 に固定して使用する場合の実行を行う。NPB3.2 OpenMP バージョンの FT ベンチマークを題材とした。性能は 2 スレッドで 1232 MFLOPS となる。(この性能値は、2005年当時の AMD Athlon プロセッサ上の性能である

$ export MP_BLIST=0,1
$ ./ft.A.numa

 NAS Parallel Benchmarks (NPB3.2-OMP) - FT Benchmark

 Size                :  256x 256x 128
 Iterations                  :    6
 Number of available threads :    2
 class = A

 FT Benchmark Completed.
 Class           =                        A
 Size            =            256x 256x 128
 Iterations      =                        6
 Time in seconds =                     5.79
 Total threads   =                        2
 Avail threads   =                        2
 Mop/s total     =                 1232.69
 Mop/s/thread    =                   616.35
 Operation type  =           floating point
 Verification    =               SUCCESSFUL
 Version         =                      3.2
 Compile date    =              23 Nov 2005

 Compile options:
    F77          = pgf90
    FLINK        = $(F77)
    FFLAGS       = -mp=numa -fastsse -Minfo -Mipa=fast
    FLINKFLAGS   = -fastsse -mp=numa -Mipa=fast  

2CPU を使用して実行しているので、二つの CPU 0, 1 が100% 使用されていることが分かる。

● MP_BLIST=0,0を設定する

並列スレッド 0 を CPU0、 並列スレッド 1 を CPU0 に同じ CPU に固定して使用する場合の実行を行う。性能は 2 スレッドで 684 MFLOPS となる。同じCPUを使用するため、並列性能は劣化する。

$ export MP_BLIST=0,0
$ ./ft.A.numa


 NAS Parallel Benchmarks (NPB3.2-OMP) - FT Benchmark

 Size                :  256x 256x 128
 Iterations                  :    6
 Number of available threads :    2

 T =    1     Checksum =    5.046735008193D+02    5.114047905510D+02
 T =    2     Checksum =    5.059412319734D+02    5.098809666433D+02
 T =    3     Checksum =    5.069376896287D+02    5.098144042213D+02
 T =    4     Checksum =    5.077892868474D+02    5.101336130759D+02
 T =    5     Checksum =    5.085233095391D+02    5.104914655194D+02
 T =    6     Checksum =    5.091487099959D+02    5.107917842803D+02
 Result verification successful
 class = A


 FT Benchmark Completed.
 Class           =                        A
 Size            =            256x 256x 128
 Iterations      =                        6
 Time in seconds =                    10.43
 Total threads   =                        2
 Avail threads   =                        2
 Mop/s total     =                  684.00
 Mop/s/thread    =                   342.00
 Operation type  =           floating point
 Verification    =               SUCCESSFUL
 Version         =                      3.2
 Compile date    =              23 Nov 2005

 Compile options:
    F77          = pgf90
    FLINK        = $(F77)
    FFLAGS       = -mp=numa -fastsse -Minfo -Mipa=fast
    FLINKFLAGS   = -fastsse -mp=numa -Mipa=fast

2CPスレッド実行であるが、 CPU0 だけを使用するように指示したため、CPU0 が100% 使用されていることが分かる。

● MP_BLIST=1,1を設定する

並列スレッド 0 を CPU1、 並列スレッド 1 を CPU1 に同じ CPU に固定して使用する場合の実行を行う。性能は 2 スレッドで 684 MFLOPS となる。同じCPUを使用するため、並列性能は劣化する。

$ export MP_BLIST=1,1
$ ./ft.A.numa


 NAS Parallel Benchmarks (NPB3.2-OMP) - FT Benchmark

 Size                :  256x 256x 128
 Iterations                  :    6
 Number of available threads :    2
 class = A

 FT Benchmark Completed.
 Class           =                        A
 Size            =            256x 256x 128
 Iterations      =                        6
 Time in seconds =                    10.43
 Total threads   =                        2
 Avail threads   =                        2
 Mop/s total     =                  684.38
 Mop/s/thread    =                   342.19
 Operation type  =           floating point
 Verification    =               SUCCESSFUL
 Version         =                      3.2
 Compile date    =              23 Nov 2005

 Compile options:
    F77          = pgf90
    FLINK        = $(F77)
    FFLAGS       = -mp=numa -fastsse -Minfo -Mipa=fast
    FLINKFLAGS   = -fastsse -mp=numa -Mipa=fast

2CPU スレッド実行であるが、 CPU1 だけを使用するように指示したため、CPU1 が100% 使用されていることが分かる。