openMPによる並列計算

まとめ


fortran90では、

program main
  implicit none
  integer::i,omp_get_thread_num

  ! number of threads for parallel computation
  call omp_set_num_threads(2)

  !$omp parallel do
  do i=1,100
     write(6,*) omp_get_thread_num()
  enddo
  !$omp end parallel do  

  stop
end program main

とすれば2個のスレッドで並列計算され、コンパイル時に
intel® fortranでは、

ifort -openmp main.f90

gfortranでは、

gfortran -fopenmp main.f90

とすれば良いです。


fortran90でのお話です。

openMPは複数のCPUを使って並列計算をする方法のことです
何も指定しない場合、計算は基本的に1つのコアのみを使います。
現在のところ、C/C++,fortranで対応しているようです。
Linuxを使っている場合、恐らくデフォルトで使えるようになっているはずです。

並列計算を行うまでの大まかな手順は2つ。

  1. プログラム内部に文章を書く
  2. コンパイル時にオプションを追加
    • intel®fortranコンパイラの場合
    • gfortranコンパイラの場合

です。

※今使っているパソコンの並列計算できるCPUの数の確認は、

cat /proc/cpuinfo | grep processor

です。proc/cpuinfoの場所は使っているOSによって違う可能性があります。
”linux mint cpu 確認”
等と検索するのが手っ取り早いでしょう。

プログラム内部に文章を書く


プログラム内でopenMPを使用するdoループを挟むように、

!$omp parallel do
do i=1,100
    call test(i)   
enddo
!$omp end parallel do

と書きます。基本はこれだけです。
並列計算に使えるスレッドが2個である場合、並列計算というのは

スレッド0

do i=1,50
    call test(i)   
enddo

スレッド1

do i=51,100
    call test(i)   
enddo

と割り振る操作なのです。

上記のようにdoループを分割するため、注意しなければならない点として、
doループに用いる変数に対して別々に計算しても計算結果に支障をきたさない事
が挙げられます。
上記例では、i=0から始めて、i=1,2,3,・・・と順次計算する必要は無く、i=5,10,1,3,・・・みたいな乱雑な順番でも計算結果が変わらない状況で使わなければなりません。

実際には同期したりとかで、出来なくはないですが面倒くさく、僕はやったことが無いので書きません。

もしも並列計算したいスレッド数を制限したい場合、

!$omp parallel do

!$omp parallel do num_threads(2)

のように指定するか(この例ではその部分の並列計算を2つのスレッドで行う)、
並列計算が始まる前のプログラムの初めの方に

call omp_set_num_threads(2)

と書きましょう。これを書くと全ての並列計算箇所が2個のスレッドで行われることになります。

また、関数omp_get_thread_num()はスレッドにつけられた番号を返す事が出来ます。引数は要りません。
なので、これを利用すると何個のスレッドが使われているのか直に確認できます。

補足ですが、MKL(マス・カーネル・ライブラリ)のルーチン内で使われる並列計算のスレッド数は

call mkl_set_num_threads(2)

と書くことで制御できます。

コンパイル時のオプション


intel® fortranでは、

ifort -openmp main.f90

gfortranでは、

gfortran -fopenmp main.f90

としてコンパイル、実行しましょう。

実例


例として

program main
  implicit none
  integer::i,omp_get_thread_num

  ! number of threads for parallel computation
  call omp_set_num_threads(2)

  !$omp parallel do
  do i=1,4
     write(6,*) omp_get_thread_num()
  enddo
  !$omp end parallel do

  stop
end program main

を動かすと同出力されるか見てみましょう。
gfortranコンパイラで、使えるスレッドの数は2つです。
上記のコードを

gfortran -fopenmp main.f90

としてコンパイルし、実行すると

./a.out
           1
           0
           0
           1

という結果が得られます。
ここに出ている数字はスレッドの番号で、0,1と番号付けされたスレッドが確かに出力されている事が分かります。1,0,0,1の順番はどうでも良いです。実行するごとに変わるので、これは同時並行処理されているためでしょう。

call omp_set_num_threads(1)

として計算を行うと、

./a.out
           0
           0
           0
           0

と表示されることから、確かに使うスレッド数が制御されていることが分かります。

doループの並列処理を処理が終わった順に行う場合


doループ内で、あるときだけ計算が重くなる場合があります。
この場合、均等にdoループの変数を分けてしまうとCPU1つあたりの処理が大きく異なってしまいます。そこで、doループの変数を終わった順に処理していく指定方法があります。それがschedule構文です。以下のようにすると、i=1から計算が終わった順にiを増やしていきます。

program main
  implicit none
  integer::i,omp_get_thread_num,N

  ! number of threads for parallel computation
  call omp_set_num_threads(2)

  N=100
  !$omp parallel do schedule(dynamic,1)
  do i=1,N
     write(6,*) omp_get_thread_num()
  enddo
  !$omp end parallel do
 
  stop
end program main

とします。ただし、1回あたり計算時間がほとんどかからない場合、オーバーヘッド時間が無視できなくなるため、使うときは吟味してから使用するべきです。

また、並列化しても共通の変数を使うことができます。以下のようにすると、計算の進み具合がわかります。

program main
  implicit none
  integer::i,omp_get_thread_num,N,prog

  ! number of threads for parallel computation
  call omp_set_num_threads(2)

  N=100
  prog=0
  !$omp parallel do shared(prog) schedule(dynamic,1)
  do i=1,N
     write(6,*) omp_get_thread_num()
     prog=prog+1
     if(mod(prog,50).eq.0)write(6,'(A,i6,A,i6)')" ",prog," / ",N
  enddo
  !$omp end parallel do
 
  stop
end program main

とします。

openMPの機能はこれだけではありません。
調べることをお勧めします。ここではこれ以上書きません。

参考文献


インテル® コンパイラーOpenMP*入門


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です