OpenMPによる並列化

近年、半導体の集積技術の限界に伴って、プロセッサ 1個あたりの演算速度の向上は鈍化傾向にあるため、複数のプロセッサに処理を分散させる並列計算が今後ますます重要になると考えられます。この記事では並列化プログラミングについて説明します。

スポンサーリンク

並列計算機のタイプ

並列計算機におけるメモリ方式には大きくわけて、(a)共有メモリ型、(b)分散メモリ型の二種類の方式を挙げることができますが、これらのメモリ方式概念図を以下に示します。


ParallelComputer

図. メモリ方式概念図


共有メモリはメモリ全体をプロセッサで共有する方式です。プロセッサはバスで結合されたメモリにアクセス出来るためアクセス速度は高速ですが、共有できるメモリのサイズには限界があります。

一方、分散メモリ方式は、メモリが分散しており、各プロセッサは自分に結合したメモリにバスを介してアクセスし、他のメモリ空間へはバス以外の通信手段を用いてデータの送受信を行います。この通信速度は一般的にバスと比較して圧倒的に遅いです。

並列化の手段

主要な並列化の手段として、以下の3つが挙げられます。

(1) 自動並列化
(2) OpenMP
(3) MPI

このうち、自動並列化とOpenMPは共有メモリ型計算機に適しており、MPIは分散メモリ型計算機に適しています。

自動並列化はプログラム改変が不要ですが、並列化しても問題無いとコンパイラが判断できた部分だけしか並列化されないため、OpenMPやMPIに比べて並列化による効果が得られ難いことが一般的です。

一方、OpenMPやMPIでは、並列化の対象となるループや部分プログラムをプログラマに指定させることにより、コンパイラだけでは判別できなかった部分の並列化を行うことができます。

ここで、MPIによる並列化を行なう場合、通常はプログラムの大きな改変を必要とし、プログラミングに要する労力が大きいです。

これに対して、OpenMPは共有メモリを利用する性質上、プログラムの大きな改変を必要としないため、比較的に容易にプログラミングできます。

また、gfortranを用いることで、OpenMPによる並列化プログラミング環境を用意に手に入れることができます。前記事を参考にgfortranをインストールした方は、既にOpenMPが使える環境が整っています。

この記事では、プログラミングが比較的容易で、環境も整えやすいOpenMPについて解説します。

サンプルプログラム

OpenMPを使ったプログラミングについては、以下のサンプルプログラムを用いて説明します。ダウンロードはこちらからお願いします(右クリックメニューから保存)。→ sample12.f90

同様に、時間計測プログラムもダウンロードして下さい。→ CPUtime_M.f90
ダウンロードしたsample12.f90とCPUtime_M.f90は同じフォルダに置いて下さい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
include './CPUtime_M.f90' ! 時間を計測する外部プログラム追加
!---------------------------- メインルーチン
program sample12
  use CPUtime_M, only: CPUtime_start, CPUtime_get
  implicit none
  integer :: nrepeat
  nrepeat = 200000000
  call CPUtime_start()                             ! 時間計測開始
  call sample12_sub_single(nrepeat)                ! サブルーチン呼び出し
  write(*,'(a,f10.4)') 'Time[s] = ', CPUtime_get() ! 時間計測終了
  call CPUtime_start()                             ! 時間計測開始
  call sample12_sub_parallel(nrepeat)              ! サブルーチン呼び出し(並列)
  write(*,'(a,f10.4)') 'Time[s] = ', CPUtime_get() ! 時間計測終了
end program sample12
 
!---------------------------- サブルーチン
subroutine sample12_sub_single(nrepeat)
  implicit none
  integer, intent(in) :: nrepeat
  integer :: i
  real(8) :: x, tx
  tx = 0.0d0
  do i = 1, nrepeat
    x = dble(i) * dble(i)
    tx = tx + x
  end do
  write(*,'(a,e15.4)') 'Result = ', tx
end subroutine sample12_sub_single
 
!---------------------------- サブルーチン(並列)
subroutine sample12_sub_parallel(nrepeat)
  implicit none
  integer, intent(in) :: nrepeat
  integer :: i, l
  integer(4) :: iam, npe, OMP_GET_THREAD_NUM, OMP_GET_NUM_THREADS
  real(8) :: x, tx, px
  tx = 0.0d0
!$OMP parallel default(private), & ! 並列計算開始
!$OMP& shared(tx,nrepeat)          ! 共有変数の指定
  iam = OMP_GET_THREAD_NUM()       ! CPU番号を取得する関数
  npe = OMP_GET_NUM_THREADS()      ! 並列計算に用いたCPUの総数
  if (iam == 0) write(*,'(a,i3)') 'Num. of CPU = ', npe
  px = 0.0d0                       ! ファーストタッチ
!$OMP do                           ! 並列loop開始
  do i = 1, nrepeat
    x = dble(i) * dble(i)
    px = px + x
  end do
!$OMP end do nowait                ! 並列loop終了
  do l = 0, npe - 1
!$OMP barrier                      ! 各CPUの同期
    if (iam == l) then
      tx = tx + px                 ! 各CPUのデータをまとめる
    end if
  end do
!$OMP end parallel                 ! 並列計算終了
  write(*,'(a,e15.4)') 'Result = ', tx
end subroutine sample12_sub_parallel


プログラムを順に説明します。

まず、1行目は、include文を用いて、時間を計測する外部プログラム追加しています。

3-14行目はメインルーチンです。並列化していないサブルーチン(17-28行目)と、並列化したサブルーチン(31-58)を順に呼び出して、計算時間の比較を行っています。

これらのサブルーチンでは、二乗の和を計算して、最後に結果を出力しています。

以下に並列化サブルーチンについて詳しく説明をします。

38-39行目: !$OMP parallelで並列計算が開始されます。また、この部分で変数を共有するか(shared)、共有しないか(private)を指定します。このサンプルプログラムではデフォルトをprivateとしています。

40-43行目: OMP_GET_THREAD_NUM()はCPU 番号を取得する関数で、OMP_GET_NUM_THREADS()は並列計算に用いたCPUの総数です。この指定部以下で初期化された変数はローカルなメモリに配置されます。これをファーストタッチといいます。並列実行領域では、各プロセッサがローカルなメモリへアクセスすることになり、プログラムの性能が向上します。

44-49行目: 並列化対象となるdo loopです。開始と終了をそれぞれ、!$OMP do文と!$OMP end do (nowait)文で行ないます。

50-56行目: 各CPUにあるデータをひとつにまとめます。!$OMP barrier文を用いて、各CPUの同期を取ります。このプログラムではprivate変数(px)をshared変数(tx)に加算しています。最後に、!$OMP end parallel文で並列計算を終了します。

コンパイルと実行

コンパイルと実行には、以下のバッチファイルを使って下さい。OpenMP用なので、今までの記事で使っていたバッチファイルとは少しだけ中身が異なります。

64bit用 → comp_exe_64_OMPx2.bat
32bit用 → comp_exe_32_OMPx2.bat


例として、64bit用を下記します。

1
2
3
4
5
6
@echo off
C:\TDM-GCC-64\bin\gfortran.exe -fopenmp -o %~n1.exe %1
del *.mod
set OMP_NUM_THREADS=2
%~n1.exe
pause


2行目がコンパイルですが、並列化オプションとしてfopenmpを用いています。
4行目のOMP_NUM_THREADSで使用するCPUの数を指定しています。この例では2CPUを使っています。

使い方は、sample12.f90をcomp_exe_64(32)_OMPx2.batにドラッグ&ドロップするだけです。


sample12

こんな画面が出たでしょうか?出ていれば正常終了です。sample12.f90の繰り返し数(nrepeat)やcomp_exe_64(32)_OMPx2.batのCPU数(OMP_NUM_THREADS)を変えて、色々試してみて下さい。

ちなみに、並列計算に使用できるCPU数は、Ctrl + Alt + Delからタスクマネージャを起動し、論理プロセッサ数の数値から知ることができます。Windows8.1の場合は、以下のような画面です。


TaskManager


← 前の記事へ(配列(2)-動的割付け)
目次(Fortran90)

スポンサーリンク

コメントをどうぞ

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