HOME > システム > SR16000 > SR16000 利用の手引き > 第9章 使用例

第9章 使用例

9.1 概要

本章ではこれまでの各章を踏まえ、SR16000 の使用例として、極めて簡単な例から一般的なパターンまでプログラムのコンパイルと実行方法を紹介いたします。
▲ 第9章 使用例 TOPへ

9.2 初歩的な例

本節はこれから初めて本センターの SR16000 にてスーパーコンピューターを使用しようとする方を対象にしています。しかしながら、Fortran 及び C言語の文法や UNIX コマンド、ホスト名などの概念について解説するものでは無く、それらは既知として想定しておりますのでご注意ください。また、MPI 等についてセンターでは講習会を定期的に開催しておりますので、そちらの参加もご検討ください。
ここで、本節については MPI、OpenMP などのノード間、ノード内並列に詳しかったり、すでに本システムにて十分に計算を行われた経験があったりする方は読み飛ばしていただいて問題はありません。

9.2.1 プログラムの作成とコンパイル

プログラムソースの例を示し、日立最適化 FORTRAN90 コンパイラを用いてコンパイルしてみます。


まず、エディタである vi や emacs を用いて、以下のように Fortran90 でプログラムを作成し、ファイル名「gethostn.f90」として保存しておきます。

 program gethostn
   integer statnum,hostnm
   character*12 name

   statnum = hostnm(name)

   write(*,*) 'HOSTNAME is "',name,'"(',statnum,')'
 end

ここでは例として、実行する計算ノードのホスト名を取得し、それを標準出力するプログラムになっています。具体的には日立最適化 FORTRAN90 コンパイラで利用可能なサービスサブルーチンの一つである HOSTNM を用いて、実行時に name 変数にホスト名を格納し、ホスト名が取得できた場合に statnum に「0」を格納します。その後、write 文にてそれぞれの値を出力します。


日立最適化 FORTRAN90 コンパイラにおいて HOSTNM のようなコンパイラでサービスするサブルーチンについては日立より用意されているマニュアル「最適化 FORTRAN 言語」及び「最適化 FORTRAN 使用の手引」に記載があります。今回の場合、「最適化 FORTRAN 言語」の索引から先頭の文字 H の箇所を参照してください。マニュアルにはこの他にも各オプションの解説や、プログラミングに必要な注意書きなどが記載してあります。適宜参照するようにしてください。


それでは、このプログラムをコンパイルするために、以下のようなオプションを含むコマンドラインを実行してください。-lf90c により、サブルーチンの HOSTNM の呼び出しが可能になるからです。

 % f90 gethostn.f90 -lf90c
 f90: compile start : gethostn.f90

 *OFORT90 V03-01-/B entered.
 *program name = GETHOSTN
 *end of compilation : GETHOSTN
 *program units = 0001, no diagnostics generated.

このコマンドラインでは上記の様に特にエラーを出力せず、実行オブジェクトとして、a.out が生成されます。ここで、プログラムソース中の statnum = hostnm(name) の一行上に意図的に間違った文法となる statnum == 1 という行を追加してコンパイルすると次のようなエラーになります。

 % f90 gethostn.f90 -lf90c
 f90: compile start : gethostn.f90

 *OFORT90 V03-01-/B entered.
    KCHF049K  12            5
              unrecognizable fortran statement.
 *program name = GETHOSTN
 *program units = 0001, 0001  diagnostics generated, highest severity code is  12

この KCHF の一行が日立最適化 FORTRAN90 コンパイラにおける診断メッセージのコードを表しており、その一行下が診断メッセージ本文となります。今回の場合はコード:KCHF049K のエラー(Fortran で分類できない文がある)が発生しており、エラーレベルは 12(文法の誤りがあり、オブジェクトモジュールは生成しない)でエラー個所はソースコードの 5 行目ということを表してします。もし、診断メッセージだけでは不十分であった場合はデバッグオプションである -debug をコンパイルオプションに追加して試してみるとより詳細な情報が得られることがあります。通常はこのメッセージを参考にプログラム作成を行ったり、本センターの SR16000 の環境で正常に動作するよう、手持ちのプログラムのデバッグ作業を実施します。


また、この診断メッセージのコードを元にマニュアルの関連する記述を参照することができます。上記の例の場合は「最適化 FORTRAN 使用の手引」の「11章コンパイル時の診断メッセージ(11.1 診断メッセージ)」の KCHF049K の項を参照してください。


次に、コンパイル可能な状態のプログラムソースを使って、試しにオプション「-lf90c」を外してコンパイルしてみます。

 % f90 gethostn.f90
 f90: compile start : gethostn.f90

 *OFORT90 V03-01-/B entered.
 *program name = GETHOSTN
 *end of compilation : GETHOSTN
 *program units = 0001, no diagnostics generated.
 ld: 0711-317 ERROR: Undefined symbol: .HOSTNM
 ld: 0711-345 Use the -bloadmap or -bnoquiet option to obtain more information.
 find: bad status-- /tmp/.20120806165543720948F90

先ほどとは異なるエラーが出ました。ld で Undefined symbol なので、HOSTNM が解釈できていないことになります。HOSTNM はプログラムソース中の hostnm と異なり、大文字と小文字の差がありますが、今回のサービスサブルーチンの場合、適宜コンパイラが解釈してくれるので、問題となる箇所ではありません。サブルーチン名の大文字と小文字を解釈してくれるオプション「-i,L」を用いると次のようになります。このオプションは今後外部のライブラリを用いる場合には必要なケースがあります。

 % f90 gethostn.f90 -i,L
 f90: compile start : gethostn.f90

 *OFORT90 V03-01-/B entered.
 *program name = gethostn
 *end of compilation : gethostn
 *program units = 0001, no diagnostics generated.
 ld: 0711-317 ERROR: Undefined symbol: .hostnm
 ld: 0711-345 Use the -bloadmap or -bnoquiet option to obtain more information.
 find: bad status-- /tmp/.201208061718375374832F90

以上より、このようなすでにコンパイル可能な例、あるいは手持ちのプログラムを用いて、オプションを変更したり、プログラムの記述をわざと変更したりすることで、本システムではどの様なエラーメッセージが出力されるかを把握しておくことは今後のプログラムのデバッグ作業にも有用となります。

9.2.2 ジョブ実行

9.2.1 でコンパイル、生成された a.out を、ジョブスクリプトを記述して、計算ノードで実行してみます。その際、ジョブスクリプトのパラメータを少し変更して出力の変化を見てみます。


ジョブスクリプト(job.csh)は以下のようにします。parallel キュー(-q parallel)において1ノード(-N 1)を用い、1 プロセス/1 ノード(-J T01,T は task の「t」)で実行してみます。$QSUB_WORKDIR は qsub コマンドを実行した際のカレントディレクトリが代入されます。今回の場合、job.csh と a.out は同じディレクトリに保存されているとします。よって、このジョブスクリプトは、計算ノードにおいて $QSUB_WORKDIR にカレントディレクトリを変更し、$QSUB_WORKDIR に置かれている a.out を実行するというジョブスクリプトになります。

 #!/bin/csh
 #@$-q parallel
 #@$-N 1
 #@$-J T01

 cd $QSUB_WORKDIR
 ./a.out

qsub job.csh にてジョブ実行を開始し、問題なく終了すると job.csh.oXXXXX (XXXXX は数字)というテキストファイルが生成されるので、それを参照してみます。以下のような 1 レコードの出力になっているはずです。

 HOSTNAME is "htcfWWcWWpWW"(           0 )

「htcfWWcWWpWW」が取得された計算ノードのホスト名です。a.out はこの計算ノード上で実行されました。次に 2 ノード(-N 2)を用いて実行してみます。

 #!/bin/csh
 #@$-q parallel
 #@$-N 2
 #@$-J T01

 cd $QSUB_WORKDIR
 ./a.out

出力されたファイルには先ほどと同様に1レコードしか得られていないはずです。これは 1 ノード(-N 1)を用い、2 プロセス/1 ノード(-J T02)で実行しても同様です。また、下記のように a.out を 2 つ実行するようにしても、-N や -J を変化させても同じホスト名で出力されたレコードが 2 つ出力されます。

 #!/bin/csh
 #@$-q parallel
 #@$-N 2
 #@$-J T01

 cd $QSUB_WORKDIR
 ./a.out &
 ./a.out &

 wait

以上より、単純に a.out を実行する限りでは NQS のパラメータを操作しても、ある 1 ノード上でしか実行されないことが分かります。ここで、本センターのシステムは、1 つの a.out を、複数の計算ノードを跨いだすべての CPU を使って実行する仕組みではありません。これは例えば OpenMP でノード内並列(スレッド並列)されたプログラムが生成するスレッドにおいても同様です。逆に並列化されていなかったり、スレッド並列化されていても 1 ノード使えれば十分なプログラムの場合は、これまでの 1 ノード、1 プロセス実行で基本的な対応ができます。次に、NQS オプションで計算ノード数個分、プロセス数個分の a.out を実行するためには mpirun コマンドを使います(MPI 実行)。

 #!/bin/csh
 #@$-q parallel
 #@$-N 2
 #@$-J T01

 cd $QSUB_WORKDIR
 mpirun ./a.out

この場合は以下の出力が得られるはずです。各計算ノードで a.out が実行できました。

 HOSTNAME is "htcfWWcWWpWW"(           0 )
 HOSTNAME is "htcfXXcXXpXX"(           0 )

また、 1 ノード(-N 1)を用い、2 プロセス/1 ノード(-J T02)で実行するとa.out をバックグラウンドで 2 つ記述して実行せずとも以下の出力が得られます。

 HOSTNAME is "htcfWWcWWpWW"(           0 )
 HOSTNAME is "htcfWWcWWpWW"(           0 )

このように mpirun を使うと、プログラムを並列に実行できますが、単純に同じ a.out を計算ノード数分あるいはプロセス個数分実行しただけで、プログラム自体が MPI で並列化されたわけではありません。一つのプログラムを効率よく複数計算ノードあるいは複数プロセスを使って実行するためには MPI を用いて適切にプログラムソースを書き換える必要があります。

▲ 第9章 使用例 TOPへ

9.3 OpenMP

9.3.1 OpenMP プログラムとコンパイル

OpenMP は、共有メモリを用いた並列化を記述するための指示文、ライブラリ、及び環境変数を規定したもので、並列実行単位にスレッドを用います。各スレッドが各CPU に分散され並列実行されます。ここでは以下のようなサンプルプログラムを作成し、ファイル名「omp_sample.f90」として保存しておきます。
OpenMP についてはマニュアル「最適化 FORTRAN 使用の手引」の「7. OpenMP」に詳細な記述があります。コマンドなどについては FAQ 「OpenMP は使えますか。使い方を教えてください。」にまとめてありますので、ご覧ください。

 program omp_sample
   implicit none
   include 'omp_lib.h'

   double precision t1, t2, diff
   integer, parameter :: N = 1000
   integer i, j, k, nt
   double precision :: a(N,N), b(N,N), c(N,N)
   double precision tmp_val

   t1 = 0.0d0
   t2 = 0.0d0

 !$omp parallel
 !$omp master
   nt = omp_get_num_threads()
 !$omp end master
 !$omp end parallel

   do i=1, N
      do j=1, N
         a(j,i) = dble(i+j)
         b(j,i) = dble(i+j)
         c(j,i) = 0.0d0
      end do
   end do

   t1 = omp_get_wtime()
 !$omp parallel
 !$omp do private(j,k,tmp_val)
   do i=1, N
      do j=1, N
         tmp_val = 0.0d0
         do k=1, N
            tmp_val = tmp_val + a(k,i) * b(j,k)
         end do
         c(j,i) = tmp_val
      end do
   end do
 !$omp end do
 !$omp end parallel
   t2 = omp_get_wtime()

   diff = t2 - t1
   print *, nt, 'thread(s), Etime is:', diff, '[s]'
 end program omp_sample

このプログラムは適当な行列積を実行し、その計算にかかった経過時間を表示するものです。実行時のスレッド数を取得する関数(omp_get_num_threads)と経過時間を取得する関数(omp_get_wtime)を使用するため、まず omp_lib.hをインクルードします。OpenMP 実行時ライブラリのヘッダファイルであるomp_lib.h に対しては標準でライブラリパスが通っているので、omp_lib.h をフルパスなどで記述する必要はありません。


コンパイルには、日立最適化 FORTRAN90 コンパイラで OpenMP ディレクティブを有効にするオプション -omp を使います。

 % f90 -omp -Oss omp_sample.f90
 f90: compile start : ./omp_sample.f90

 *OFORT90 V03-01-/B entered.
    KCHF475K  00               C
              the variable is defined, but is never referred.
 *program name = OMP_SAMPLE
 *end of compilation : OMP_SAMPLE
 *end of compilation : _user_parallel_func_1__hf_main
 *end of compilation : _user_parallel_func_2__hf_main
 *program units = 0001, 0001  diagnostics generated, highest severity code is  00

行列積の結果を保存した配列 c の出力などを行っていないため、KCHF475Kが出力されます。これはエラーではなく警告メッセージ相当の出力であり、コンパイルは中断せず、オブジェクトファイルは生成されます。詳細は「最適化 FORTRAN 使用の手引」を参照してください。


-Oss で最適化しない場合は -Oss に含まれていた -parallel を明記します。

 % f90 -omp -parallel omp_sample.f90

9.3.2 ジョブ実行

以下のようなジョブスクリプト(job.csh)を記述します。parallel キュー(-q parallel)において1ノード(-N 1)を用い、1 プロセス/1 ノード(-J T01)で実行します。

 #!/bin/csh
 #@$-q  parallel
 #@$-N  1
 #@$-J  T01

 setenv OMP_NUM_THREADS 8

 cd $QSUB_WORKDIR
 ./a.out

OpenMP を使用したプログラムを実行する際のスレッド数は環境変数の $OMP_NUM_THREADSに与えます。上記では 1 プロセス立ち上げたプログラムから 8 つのスレッドを生成し、並列実行するものです。出力ファイル(job.csh.oXXXXX)を確認します。

 8 thread(s), Etime is:  0.412459999999999938      [s]

$OMP_NUM_THREADS を 1 から 64 まで変化させてそれぞれ実行し、結果を比較してみてください。ここで、基本的に性能面での理由から、指定する $OMP_NUM_THREADS の値が32(CPU コア数) x SMT(=2) = 64 を超えないように注意してください。

▲ 第9章 使用例 TOPへ

9.4 MPI

9.4.1 簡単な MPI プログラムとコンパイル

MPI(Message Passing Interface) は MPI Forum によって標準化されたメッセージ通信ライブラリのインタフェース規約です。ノード間及びノード内のプロセス間通信に MPI 通信ライブラリを持ちいたメッセージ通信が可能です。MPIは多くの計算機に実装されており、移植性の高いインタフェースとなります。
本センターの SR16000 では MPI 2.1 をサポートしています。ここでは以下のようなサンプルプログラムを作成し、ファイル名「mpi_sample.f90」として保存しておきます。コマンドなどの詳細については、FAQ 「MPI プログラムをコンパイルする際のコマンド名について教えてください。」をご覧ください。

 program getmpirank
   implicit none
   include 'mpif.h'

   integer :: size,rank,ierr

   call MPI_INIT(ierr)
   call MPI_COMM_RANK(MPI_COMM_WORLD,rank,ierr)
   call MPI_COMM_SIZE(MPI_COMM_WORLD,size,ierr)

   write(*,*) 'proc=',rank, 'size=',size

   call MPI_FINALIZE(ierr)
 end program getmpirank

MPI 関数を使用するため、まず mpif.h をインクルードします。MPI ライブラリのヘッダファイルである mpif.h に対しては標準でライブラリパスが通っているので、mpif.h をフルパスなどで記述する必要はありません。上記では、MPI実行環境の初期化(MPI_INIT)と実行環境の終了(MPI_FINALIZE)、MPI コミュニケータから、call したプロセスのランクの取得(MPI_COMM_RANK)と全プロセス数の取得(MPI_COMM_SIZE)というプログラム制御関連の MPI 関数のみ用いており、通信を行う関数(MPI_Send など)を使用していない単純な例となっています。また、MPI コミュニケータには全プロセスで構成される MPI_COMM_WORLD を指定しています。取得できるランク番号は 0 から始まる点ご注意ください。


コンパイルには、MPI を使用するにあたって必要なヘッダファイルやライブラリ、コンパイルオプションを設定し、f90 コンパイラを呼び出す mpif90 コマンドを使います。今回は以下のように -Oss で最適化するものの、このオプションには自動並列化オプションが含まれるため、自動並列化を -noparallel オプションで無効にします。ピュア MPI なので、各プロセスは単一スレッドでの動作となります。

 % mpif90 -Oss -noparallel mpi_sample.f90
 f90: compile start : mpi_sample.f90

 *OFORT90 V03-01-/B entered.
 *program name = GETMPIRANK
 *end of compilation : GETMPIRANK
 *program units = 0001, no diagnostics generated.

9.4.2 ジョブ実行(ピュア MPI)

以下のようなジョブスクリプト(job.csh)を記述します。parallel キュー(-q parallel)において2ノード(-N 2)を用い、32 プロセス/1 ノード(-J T32)で実行します。

 #!/bin/csh
 #@$-q  parallel
 #@$-N  2
 #@$-J  T32

 cd $QSUB_WORKDIR
 mpirun ./a.out

合計 64 プロセスでの実行なので、出力されるランク番号は 0 から 63 の 64 個になっています。出力ファイル(job.csh.oXXXXX)を確認します。

 proc=           0 size=          64
  proc=          1 size=          64
 ....
  proc=         62 size=          64
  proc=         63 size=          64

ここで、指定するプロセス数(-J)には 32(CPU コア数) x SMT(=2) = 64 を超えた指定を行うことはできません。

9.4.3 OpenMP + MPI ハイブリッド実行

OpenMP + MPI ハイブリッド実行についてはコンパイル方法とジョブスクリプトの例を示すに留めることにします。プログラムソース(hybrid_sample.f90)には OpenMP ディレクティブが書かれているとすると、次のようにしてコンパイルします。なお、コマンドなどについては、FAQ 「MPI 並列化したプログラムですが、ノード内を OpenMP で並列化していますが利用可能でしょうか。また、コンパイル方法について教えてください。」にもまとめてありますので、ご覧ください。

 % mpif90 -omp -Oss hybrid_sample.f90

-Oss で最適化しない場合は -Oss に含まれていた -parallel を明記します。

 % mpif90 -omp -parallel hybrid_sample.f90

このプログラムを 1 ノードあたり 8 プロセス、 1 プロセスあたり 4 スレッド、8 ノードで実行する際のジョブスクリプトは以下のようになります。

 #!/bin/csh
 #@$-q  parallel
 #@$-N  8
 #@$-J  T08

 setenv OMP_NUM_THREADS 4

 cd $QSUB_WORKDIR
 mpirun ./a.out

parallel キュー(-q parallel)において 8 ノード(-N 8)を用い、8 プロセス/1 ノード(-J T08)での実行となります。また、各プロセスは OpenMP のスレッド 4 本で並列化するので、環境変数 OMP_NUM_THREADS に 4 を設定します。


ここで、プログラムの作りにもよりますが、基本的に性能面での理由から、指定するプロセス数(-J) x OMP_NUM_THREADS の値が32(CPU コア数) x SMT(=2) = 64 を超えないように注意してください。プログラムの作り次第で計算ノード上のメモリを使い果たしてしまい、一部プロセスがダウンしてしまうなどの原因になります。

▲ 第9章 使用例 TOPへ

9.5 より高度な実行

立ち上げるプロセスやスレッドを適切な CPU に割り付け、かつ、物理的に最適なメモリを使うようにするにはどのようにすればよいかや SMT についてなどが日立より公開されているマニュアル「大規模 SMP 並列 スーパーコンピューターシステム ジョブ実行方法」や「SR16000 性能を引き出す利用方法」に書かれていますので、こちらを参照の上ご活用ください。
▲ 第9章 使用例 TOPへ