まとめ
fortran90では、
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では、
gfortranでは、
とすれば良いです。
fortran90でのお話です。
openMPは複数のCPUを使って並列計算をするインターフェースのことです
C/C++,fortranで対応しています。
何も指定しない場合、計算は基本的に1つのコアのみを使います。
Linuxを使っている場合、恐らくデフォルトで使えるようになっているはずです。
並列計算を行うまでの大まかな手順は2つ。
- プログラム内部に文章を書く
- コンパイル時にオプションを追加
- intel®fortranコンパイラの場合
- gfortranコンパイラの場合
です。
※今使っているパソコンの並列計算できるCPUの数の確認は、
です。proc/cpuinfoの場所は使っているOSによって違う可能性があります。
”linux mint cpu 確認”
等とgoogle等で検索するのが手っ取り早いでしょう。
プログラム内部に文章を書く
プログラム内でopenMPを使用するdoループを挟むように、
do i=1,100
call test(i)
enddo
!$omp end parallel do
と書きます。基本はこれだけです。
並列計算に使えるスレッドが2個である場合、並列計算というのは
スレッド0
call test(i)
enddo
スレッド1
call test(i)
enddo
と割り振る操作なのです。
上記のようにdoループを分割するため、注意しなければならない点として、
doループに用いる変数に対して別々に計算しても計算結果が変わらない事
が挙げられます。
上記例では、i=0から始めて、i=1,2,3,・・・と順次計算する必要は無く、i=5,10,1,3,・・・みたいな乱雑な順番でも計算結果が変わらない状況で使わなければなりません。
特に、i=2を計算したい時にi=1の計算結果を使う場合は並列化することは出来ません。
実際には同期したりとかで、出来なくはないですが面倒くさく、僕はやったことが無いので書きません。
もしも並列計算したいスレッド数を制限したい場合、
を
のように指定するか(この例ではその部分の並列計算を2つのスレッドで行う)、
並列計算が始まる前のプログラムの初めの方に
と書きましょう。これを書くと全ての並列計算箇所が2個のスレッドで行われることになります。
また、関数omp_get_thread_num()はスレッドにつけられた番号を返す事が出来ます。引数は要りません。
なので、これを利用すると何個のスレッドが使われているのか直に確認できます。
補足ですが、MKL(マス・カーネル・ライブラリ)のルーチン内で使われる並列計算のスレッド数は
と書くことで制御できます。
コンパイル時のオプション
intel® fortranでは、
gfortranでは、
としてコンパイル、実行しましょう。
実例
例として
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つです。
上記のコードを
としてコンパイルし、実行すると
1
0
0
1
という結果が得られます。
ここに出ている数字はスレッドの番号で、0,1と番号付けされたスレッドが確かに出力されている事が分かります。1,0,0,1の順番はどうでも良いです。実行するごとに変わるので、これは同時並行処理されているためでしょう。
として計算を行うと、
0
0
0
0
と表示されることから、確かに使うスレッド数が制御されていることが分かります。
doループの並列処理を処理が終わった順に行う場合
doループ内で、あるときだけ計算が重くなる場合があります。
この場合、均等にdoループの変数を分けてしまうとCPU1つあたりの処理が大きく異なってしまいます。そこで、doループの変数を終わった順に処理していく指定方法があります。それがschedule構文です。以下のようにすると、i=1から計算が終わった順にiを増やしていきます。
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回あたり計算時間がほとんどかからない場合、オーバーヘッド時間が無視できなくなるため、使うときは吟味してから使用するべきです。
また、並列化しても共通の変数を使うことができます。以下のようにすると、計算の進み具合がわかります。
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を使用する際はただ一つのサブルーチンを用いて、並列個所を簡単にするのが良いです。
do i=1,N
a(i)=0
do j=i,N
a(i)=a(i)+1
enddo
a(i)=a(i)*i
enddo
!$omp end parallel do
と、!$~!$につらつらと書くのではなく、
do i=1,N
call test(i,N,a(i))
enddo
!$omp end parallel do
として、サブルーチン1つにするだけの方がバグが出にくいです。
openMPの機能はこれだけではありません。
調べることをお勧めします。ここではこれ以上書きません。