並列プログラミング 入門(openmp...2020/02/03 · 並列プログラミング...
TRANSCRIPT
並列プログラミング入門(OpenMP編)
高度情報科学技術研究機構
1登録施設利用促進機関 文科省委託事業「HPCIの運営」代表機関一般財団法人 高度情報科学技術研究機構(RIST)
2
2018年10月
一般財団法人高度情報科学技術研究機構 (著作者 )
本資料を教育目的等で利用いただいて構いません利用に際しては以下の点に留意いただくとともに下記のヘルプデスクにお問い合わせ下さい◼ 本資料は構成文章画像などの全てにおいて著作権法上の保護を受
けています◼ 本資料の一部あるいは全部についていかなる方法においても無断での
転載複製を禁じます◼ 本資料に記載された内容などは予告なく変更される場合があります◼ 本資料に起因して使用者に直接または間接的損害が生じても著作者は
いかなる責任も負わないものとします
問い合わせ先ヘルプデスク helpdesk[-at-]hpci-officejp([-at-]をにしてください)
はじめに
HPC用コンピュータの典型的な構成
複数のコンピュータ群から構成
◼ 1台1台をノードと呼ぶ
◼ ハードウェア(アーキテクチャ)の特性を利用するにはノード間の並列化が重要
CPUに複数プロセッサーコアが搭載
◼ マルチコアCPUと呼ぶ (パソコンでも主流)
◼ ハードウェア (アーキテクチャ) の特性を利用するにはノード内の並列化が重要
本資料
◼ ノード内の並列列化に多く用いられるOpenMPの入門的内容
3
CPU
Node
Core
Core
Core
Core
CPU
Node
Core
Core
Core
Core
CPU
Node
Core
Core
Core
Core
CPU
Node
Core
Core
Core
Core
Network
(Interconnect)
実際のHPC用コンピュータはさらに複合的な構成bull ノード内がソケット (コアのグループ) で分離 NUMA (Non-Uniform Memory Access) 構成bull 各ノードにCPUとは別の計算ユニットが追加 アクセラレータ (例 GPGPU)
Outline4
はじめに
並列処理
OpenMP入門
並列実行領域中のデータの属性
アクセス競合に注意すべきループ
ループの並列化と依存性
Reduction演算
オーバヘッドとロードバランス
同期 並列処理の制御
実行時ライブラリルーチンと環境変数
並列処理の形態を説明します
並列処理5
並列処理とは6
処理を分割して同時並列に実行すること
処理終了までの時間の短縮が目的
マルチコア環境では各コアに処理を分配したい
逐次
時間
処理
処理
処理
処理
4並列
処理 処理 処理 処理
プロセスとスレッド7
スレッド
プロセス (実行中のプログラム) の中に含まれるより小さい実行単位(処理の分割単位)
1プロセス内のスレッドはメモリ空間を共有する
1つのスレッドは1つのコアで実行される
逐次実行中(通常)のプロセス(シングルスレッド)
メモリ空間
スレッド
プロセス
メモリ空間
並列実行中のプロセス(2スレッド)
プロセス
スレッド スレッド
プロセスとスレッド8
スレッド
デュアルコアのCPUで考えると
マルチスレッドのプロセスはマルチコアの性能を引き出せる
1スレッドプロセスを処理中の2コアCPU
2スレッドプロセスを処理中の2コアCPU
稼働中 空き
スレッド
稼働中 稼働中
スレッド スレッド
CPU CPU
コア コア コア コア
おもな並列化方式9
プロセス並列スレッド並列
メモリ空間
メモリ空間
プロセス プロセス
プロセス間通信
メモリ空間
スレッド スレッド
ノード間の並列(分散メモリ並列 ノード内の並列も可)
メモリ空間が別々
ノード内の並列(共有メモリ並列)
メモリ空間は一つ (共通)
MPI(Message Passing Interface)OpenMP自動並列
プロセス
スレッド スレッド
おもな並列化方式(contrsquod)10
ハイブリッド並列
プロセス間通信
メモリ空間
スレッド スレッド
プロセス
メモリ空間
スレッド スレッド
プロセス
ノード ノード
ノード内 OpenMP (または自動並列)
ノード間 MPI
通信量通信回数の削減プロセス当たりのメモリ確保
OpenMPのHello worldプログラムと
ループの並列化を紹介します
OpenMP入門11
OpenMPによる並列化12
並列化したい部分に構文 (Construct) を挿入
OpenMP構文による並列化
備考 Constructの和訳あれこれ構文 指示文
do i = 1 4000A(i) = B(i) + C(i)
end do
$omp parallel dodo i = 1 4000
A(i) = B(i) + C(i)end do$omp end parallel do
for (i=0 ilt4000 i++) a[i] = b[i] + b[i]
pragma omp parallel forfor (i=0 ilt4000 i++)
a[i] = b[i] + c[i]
Fortran C
OpenMPによる並列化 (contrsquod)13
並列化したい部分に関数があってもよい (2個の関数を同時実行)
call worker (A)call worker (B)
Fortran C
$omp parallel sections$omp sectioncall worker(A)$omp sectioncall worker(B)
$omp end parallel sections
worker (ampa)worker (ampb)subroutine worker(X)
integer XX = X + 1
end
void worker(int x)
x += 1
pragma omp parallel sections
pragma omp sectionworker (ampa)pragma omp sectionworker (ampb)
代表的なOpenMP構文(Fortran)14
代表的なOpenMP構文(Fortran)
$omp parallel $omp end parallel
$omp do
$omp parallel do
$omp parallel do reduction(+ helliphellip )
$omp sections
$omp critical
$omp barrier
$omp single
代表的なOpenMP構文(CC++)15
代表的なOpenMP構文(CC++)
pragma omp parallel
pragma omp for
pragma omp parallel for
pragma omp parallel for reduction(+ helliphellip )
pragma omp sections
pragma omp critical
pragma omp barrier
pragma omp single
OpenMPの仕様16
OpenMPの仕様を正確に知るには 規格書を読む必要
OpenMPの仕様情報 httpwwwopenmporgspecifications
最新の規格 OpenMP version 50 (v30 日本語訳あり)
コンパイラの対応状況
◼ GNU 44 v30準拠 47 v31準拠 491 v40準拠
◼ Intel 120以降 v31準拠 170以降 v45準拠
◼ LLVM Clang 60 v31準拠 Clang 70 v45準拠◼ 参考ページ httpswwwopenmporgresourcesopenmp-compilers-tools
◼ 各種情報は随時変更する可能性があります
本資料の主要部 v25までカバー
◼ 「最も基本的」な部分 (まず最初に習得すべき内容) を選択◼ 本講習の範囲外の話題例 タスク並列 ベクトル化 (SIMD) GPU対応
並列実行領域(parallel構文)17
parallel構文
Fortran $omp parallel [指示節 [[] 指示節]]
CC++ pragma parallel [指示節 [[] 指示節]]指示節 (clause) は必要に応じて指定
例 $omp parallel
$omp parallel shared(A)
$omp parallel default (shared) private(i)
printf (ldquoHello OpenMP worldyennrdquo)
pragma omp parallel
printf (ldquoHello OpenMP worldyennrdquo)
write () lsquoHello OpenMP
$omp parallel
write () lsquoHello OpenMP worldrsquo
$omp end parallel
parallelとend parallelで挟まれた部分= 並列実行領域 (parallel region)
parallel直下のブロック文 ( hellip)
= 並列実行領域 (parallel region)
備考 clauseの和訳あれこれ指示節 指示句 節
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
2
2018年10月
一般財団法人高度情報科学技術研究機構 (著作者 )
本資料を教育目的等で利用いただいて構いません利用に際しては以下の点に留意いただくとともに下記のヘルプデスクにお問い合わせ下さい◼ 本資料は構成文章画像などの全てにおいて著作権法上の保護を受
けています◼ 本資料の一部あるいは全部についていかなる方法においても無断での
転載複製を禁じます◼ 本資料に記載された内容などは予告なく変更される場合があります◼ 本資料に起因して使用者に直接または間接的損害が生じても著作者は
いかなる責任も負わないものとします
問い合わせ先ヘルプデスク helpdesk[-at-]hpci-officejp([-at-]をにしてください)
はじめに
HPC用コンピュータの典型的な構成
複数のコンピュータ群から構成
◼ 1台1台をノードと呼ぶ
◼ ハードウェア(アーキテクチャ)の特性を利用するにはノード間の並列化が重要
CPUに複数プロセッサーコアが搭載
◼ マルチコアCPUと呼ぶ (パソコンでも主流)
◼ ハードウェア (アーキテクチャ) の特性を利用するにはノード内の並列化が重要
本資料
◼ ノード内の並列列化に多く用いられるOpenMPの入門的内容
3
CPU
Node
Core
Core
Core
Core
CPU
Node
Core
Core
Core
Core
CPU
Node
Core
Core
Core
Core
CPU
Node
Core
Core
Core
Core
Network
(Interconnect)
実際のHPC用コンピュータはさらに複合的な構成bull ノード内がソケット (コアのグループ) で分離 NUMA (Non-Uniform Memory Access) 構成bull 各ノードにCPUとは別の計算ユニットが追加 アクセラレータ (例 GPGPU)
Outline4
はじめに
並列処理
OpenMP入門
並列実行領域中のデータの属性
アクセス競合に注意すべきループ
ループの並列化と依存性
Reduction演算
オーバヘッドとロードバランス
同期 並列処理の制御
実行時ライブラリルーチンと環境変数
並列処理の形態を説明します
並列処理5
並列処理とは6
処理を分割して同時並列に実行すること
処理終了までの時間の短縮が目的
マルチコア環境では各コアに処理を分配したい
逐次
時間
処理
処理
処理
処理
4並列
処理 処理 処理 処理
プロセスとスレッド7
スレッド
プロセス (実行中のプログラム) の中に含まれるより小さい実行単位(処理の分割単位)
1プロセス内のスレッドはメモリ空間を共有する
1つのスレッドは1つのコアで実行される
逐次実行中(通常)のプロセス(シングルスレッド)
メモリ空間
スレッド
プロセス
メモリ空間
並列実行中のプロセス(2スレッド)
プロセス
スレッド スレッド
プロセスとスレッド8
スレッド
デュアルコアのCPUで考えると
マルチスレッドのプロセスはマルチコアの性能を引き出せる
1スレッドプロセスを処理中の2コアCPU
2スレッドプロセスを処理中の2コアCPU
稼働中 空き
スレッド
稼働中 稼働中
スレッド スレッド
CPU CPU
コア コア コア コア
おもな並列化方式9
プロセス並列スレッド並列
メモリ空間
メモリ空間
プロセス プロセス
プロセス間通信
メモリ空間
スレッド スレッド
ノード間の並列(分散メモリ並列 ノード内の並列も可)
メモリ空間が別々
ノード内の並列(共有メモリ並列)
メモリ空間は一つ (共通)
MPI(Message Passing Interface)OpenMP自動並列
プロセス
スレッド スレッド
おもな並列化方式(contrsquod)10
ハイブリッド並列
プロセス間通信
メモリ空間
スレッド スレッド
プロセス
メモリ空間
スレッド スレッド
プロセス
ノード ノード
ノード内 OpenMP (または自動並列)
ノード間 MPI
通信量通信回数の削減プロセス当たりのメモリ確保
OpenMPのHello worldプログラムと
ループの並列化を紹介します
OpenMP入門11
OpenMPによる並列化12
並列化したい部分に構文 (Construct) を挿入
OpenMP構文による並列化
備考 Constructの和訳あれこれ構文 指示文
do i = 1 4000A(i) = B(i) + C(i)
end do
$omp parallel dodo i = 1 4000
A(i) = B(i) + C(i)end do$omp end parallel do
for (i=0 ilt4000 i++) a[i] = b[i] + b[i]
pragma omp parallel forfor (i=0 ilt4000 i++)
a[i] = b[i] + c[i]
Fortran C
OpenMPによる並列化 (contrsquod)13
並列化したい部分に関数があってもよい (2個の関数を同時実行)
call worker (A)call worker (B)
Fortran C
$omp parallel sections$omp sectioncall worker(A)$omp sectioncall worker(B)
$omp end parallel sections
worker (ampa)worker (ampb)subroutine worker(X)
integer XX = X + 1
end
void worker(int x)
x += 1
pragma omp parallel sections
pragma omp sectionworker (ampa)pragma omp sectionworker (ampb)
代表的なOpenMP構文(Fortran)14
代表的なOpenMP構文(Fortran)
$omp parallel $omp end parallel
$omp do
$omp parallel do
$omp parallel do reduction(+ helliphellip )
$omp sections
$omp critical
$omp barrier
$omp single
代表的なOpenMP構文(CC++)15
代表的なOpenMP構文(CC++)
pragma omp parallel
pragma omp for
pragma omp parallel for
pragma omp parallel for reduction(+ helliphellip )
pragma omp sections
pragma omp critical
pragma omp barrier
pragma omp single
OpenMPの仕様16
OpenMPの仕様を正確に知るには 規格書を読む必要
OpenMPの仕様情報 httpwwwopenmporgspecifications
最新の規格 OpenMP version 50 (v30 日本語訳あり)
コンパイラの対応状況
◼ GNU 44 v30準拠 47 v31準拠 491 v40準拠
◼ Intel 120以降 v31準拠 170以降 v45準拠
◼ LLVM Clang 60 v31準拠 Clang 70 v45準拠◼ 参考ページ httpswwwopenmporgresourcesopenmp-compilers-tools
◼ 各種情報は随時変更する可能性があります
本資料の主要部 v25までカバー
◼ 「最も基本的」な部分 (まず最初に習得すべき内容) を選択◼ 本講習の範囲外の話題例 タスク並列 ベクトル化 (SIMD) GPU対応
並列実行領域(parallel構文)17
parallel構文
Fortran $omp parallel [指示節 [[] 指示節]]
CC++ pragma parallel [指示節 [[] 指示節]]指示節 (clause) は必要に応じて指定
例 $omp parallel
$omp parallel shared(A)
$omp parallel default (shared) private(i)
printf (ldquoHello OpenMP worldyennrdquo)
pragma omp parallel
printf (ldquoHello OpenMP worldyennrdquo)
write () lsquoHello OpenMP
$omp parallel
write () lsquoHello OpenMP worldrsquo
$omp end parallel
parallelとend parallelで挟まれた部分= 並列実行領域 (parallel region)
parallel直下のブロック文 ( hellip)
= 並列実行領域 (parallel region)
備考 clauseの和訳あれこれ指示節 指示句 節
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
はじめに
HPC用コンピュータの典型的な構成
複数のコンピュータ群から構成
◼ 1台1台をノードと呼ぶ
◼ ハードウェア(アーキテクチャ)の特性を利用するにはノード間の並列化が重要
CPUに複数プロセッサーコアが搭載
◼ マルチコアCPUと呼ぶ (パソコンでも主流)
◼ ハードウェア (アーキテクチャ) の特性を利用するにはノード内の並列化が重要
本資料
◼ ノード内の並列列化に多く用いられるOpenMPの入門的内容
3
CPU
Node
Core
Core
Core
Core
CPU
Node
Core
Core
Core
Core
CPU
Node
Core
Core
Core
Core
CPU
Node
Core
Core
Core
Core
Network
(Interconnect)
実際のHPC用コンピュータはさらに複合的な構成bull ノード内がソケット (コアのグループ) で分離 NUMA (Non-Uniform Memory Access) 構成bull 各ノードにCPUとは別の計算ユニットが追加 アクセラレータ (例 GPGPU)
Outline4
はじめに
並列処理
OpenMP入門
並列実行領域中のデータの属性
アクセス競合に注意すべきループ
ループの並列化と依存性
Reduction演算
オーバヘッドとロードバランス
同期 並列処理の制御
実行時ライブラリルーチンと環境変数
並列処理の形態を説明します
並列処理5
並列処理とは6
処理を分割して同時並列に実行すること
処理終了までの時間の短縮が目的
マルチコア環境では各コアに処理を分配したい
逐次
時間
処理
処理
処理
処理
4並列
処理 処理 処理 処理
プロセスとスレッド7
スレッド
プロセス (実行中のプログラム) の中に含まれるより小さい実行単位(処理の分割単位)
1プロセス内のスレッドはメモリ空間を共有する
1つのスレッドは1つのコアで実行される
逐次実行中(通常)のプロセス(シングルスレッド)
メモリ空間
スレッド
プロセス
メモリ空間
並列実行中のプロセス(2スレッド)
プロセス
スレッド スレッド
プロセスとスレッド8
スレッド
デュアルコアのCPUで考えると
マルチスレッドのプロセスはマルチコアの性能を引き出せる
1スレッドプロセスを処理中の2コアCPU
2スレッドプロセスを処理中の2コアCPU
稼働中 空き
スレッド
稼働中 稼働中
スレッド スレッド
CPU CPU
コア コア コア コア
おもな並列化方式9
プロセス並列スレッド並列
メモリ空間
メモリ空間
プロセス プロセス
プロセス間通信
メモリ空間
スレッド スレッド
ノード間の並列(分散メモリ並列 ノード内の並列も可)
メモリ空間が別々
ノード内の並列(共有メモリ並列)
メモリ空間は一つ (共通)
MPI(Message Passing Interface)OpenMP自動並列
プロセス
スレッド スレッド
おもな並列化方式(contrsquod)10
ハイブリッド並列
プロセス間通信
メモリ空間
スレッド スレッド
プロセス
メモリ空間
スレッド スレッド
プロセス
ノード ノード
ノード内 OpenMP (または自動並列)
ノード間 MPI
通信量通信回数の削減プロセス当たりのメモリ確保
OpenMPのHello worldプログラムと
ループの並列化を紹介します
OpenMP入門11
OpenMPによる並列化12
並列化したい部分に構文 (Construct) を挿入
OpenMP構文による並列化
備考 Constructの和訳あれこれ構文 指示文
do i = 1 4000A(i) = B(i) + C(i)
end do
$omp parallel dodo i = 1 4000
A(i) = B(i) + C(i)end do$omp end parallel do
for (i=0 ilt4000 i++) a[i] = b[i] + b[i]
pragma omp parallel forfor (i=0 ilt4000 i++)
a[i] = b[i] + c[i]
Fortran C
OpenMPによる並列化 (contrsquod)13
並列化したい部分に関数があってもよい (2個の関数を同時実行)
call worker (A)call worker (B)
Fortran C
$omp parallel sections$omp sectioncall worker(A)$omp sectioncall worker(B)
$omp end parallel sections
worker (ampa)worker (ampb)subroutine worker(X)
integer XX = X + 1
end
void worker(int x)
x += 1
pragma omp parallel sections
pragma omp sectionworker (ampa)pragma omp sectionworker (ampb)
代表的なOpenMP構文(Fortran)14
代表的なOpenMP構文(Fortran)
$omp parallel $omp end parallel
$omp do
$omp parallel do
$omp parallel do reduction(+ helliphellip )
$omp sections
$omp critical
$omp barrier
$omp single
代表的なOpenMP構文(CC++)15
代表的なOpenMP構文(CC++)
pragma omp parallel
pragma omp for
pragma omp parallel for
pragma omp parallel for reduction(+ helliphellip )
pragma omp sections
pragma omp critical
pragma omp barrier
pragma omp single
OpenMPの仕様16
OpenMPの仕様を正確に知るには 規格書を読む必要
OpenMPの仕様情報 httpwwwopenmporgspecifications
最新の規格 OpenMP version 50 (v30 日本語訳あり)
コンパイラの対応状況
◼ GNU 44 v30準拠 47 v31準拠 491 v40準拠
◼ Intel 120以降 v31準拠 170以降 v45準拠
◼ LLVM Clang 60 v31準拠 Clang 70 v45準拠◼ 参考ページ httpswwwopenmporgresourcesopenmp-compilers-tools
◼ 各種情報は随時変更する可能性があります
本資料の主要部 v25までカバー
◼ 「最も基本的」な部分 (まず最初に習得すべき内容) を選択◼ 本講習の範囲外の話題例 タスク並列 ベクトル化 (SIMD) GPU対応
並列実行領域(parallel構文)17
parallel構文
Fortran $omp parallel [指示節 [[] 指示節]]
CC++ pragma parallel [指示節 [[] 指示節]]指示節 (clause) は必要に応じて指定
例 $omp parallel
$omp parallel shared(A)
$omp parallel default (shared) private(i)
printf (ldquoHello OpenMP worldyennrdquo)
pragma omp parallel
printf (ldquoHello OpenMP worldyennrdquo)
write () lsquoHello OpenMP
$omp parallel
write () lsquoHello OpenMP worldrsquo
$omp end parallel
parallelとend parallelで挟まれた部分= 並列実行領域 (parallel region)
parallel直下のブロック文 ( hellip)
= 並列実行領域 (parallel region)
備考 clauseの和訳あれこれ指示節 指示句 節
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
Outline4
はじめに
並列処理
OpenMP入門
並列実行領域中のデータの属性
アクセス競合に注意すべきループ
ループの並列化と依存性
Reduction演算
オーバヘッドとロードバランス
同期 並列処理の制御
実行時ライブラリルーチンと環境変数
並列処理の形態を説明します
並列処理5
並列処理とは6
処理を分割して同時並列に実行すること
処理終了までの時間の短縮が目的
マルチコア環境では各コアに処理を分配したい
逐次
時間
処理
処理
処理
処理
4並列
処理 処理 処理 処理
プロセスとスレッド7
スレッド
プロセス (実行中のプログラム) の中に含まれるより小さい実行単位(処理の分割単位)
1プロセス内のスレッドはメモリ空間を共有する
1つのスレッドは1つのコアで実行される
逐次実行中(通常)のプロセス(シングルスレッド)
メモリ空間
スレッド
プロセス
メモリ空間
並列実行中のプロセス(2スレッド)
プロセス
スレッド スレッド
プロセスとスレッド8
スレッド
デュアルコアのCPUで考えると
マルチスレッドのプロセスはマルチコアの性能を引き出せる
1スレッドプロセスを処理中の2コアCPU
2スレッドプロセスを処理中の2コアCPU
稼働中 空き
スレッド
稼働中 稼働中
スレッド スレッド
CPU CPU
コア コア コア コア
おもな並列化方式9
プロセス並列スレッド並列
メモリ空間
メモリ空間
プロセス プロセス
プロセス間通信
メモリ空間
スレッド スレッド
ノード間の並列(分散メモリ並列 ノード内の並列も可)
メモリ空間が別々
ノード内の並列(共有メモリ並列)
メモリ空間は一つ (共通)
MPI(Message Passing Interface)OpenMP自動並列
プロセス
スレッド スレッド
おもな並列化方式(contrsquod)10
ハイブリッド並列
プロセス間通信
メモリ空間
スレッド スレッド
プロセス
メモリ空間
スレッド スレッド
プロセス
ノード ノード
ノード内 OpenMP (または自動並列)
ノード間 MPI
通信量通信回数の削減プロセス当たりのメモリ確保
OpenMPのHello worldプログラムと
ループの並列化を紹介します
OpenMP入門11
OpenMPによる並列化12
並列化したい部分に構文 (Construct) を挿入
OpenMP構文による並列化
備考 Constructの和訳あれこれ構文 指示文
do i = 1 4000A(i) = B(i) + C(i)
end do
$omp parallel dodo i = 1 4000
A(i) = B(i) + C(i)end do$omp end parallel do
for (i=0 ilt4000 i++) a[i] = b[i] + b[i]
pragma omp parallel forfor (i=0 ilt4000 i++)
a[i] = b[i] + c[i]
Fortran C
OpenMPによる並列化 (contrsquod)13
並列化したい部分に関数があってもよい (2個の関数を同時実行)
call worker (A)call worker (B)
Fortran C
$omp parallel sections$omp sectioncall worker(A)$omp sectioncall worker(B)
$omp end parallel sections
worker (ampa)worker (ampb)subroutine worker(X)
integer XX = X + 1
end
void worker(int x)
x += 1
pragma omp parallel sections
pragma omp sectionworker (ampa)pragma omp sectionworker (ampb)
代表的なOpenMP構文(Fortran)14
代表的なOpenMP構文(Fortran)
$omp parallel $omp end parallel
$omp do
$omp parallel do
$omp parallel do reduction(+ helliphellip )
$omp sections
$omp critical
$omp barrier
$omp single
代表的なOpenMP構文(CC++)15
代表的なOpenMP構文(CC++)
pragma omp parallel
pragma omp for
pragma omp parallel for
pragma omp parallel for reduction(+ helliphellip )
pragma omp sections
pragma omp critical
pragma omp barrier
pragma omp single
OpenMPの仕様16
OpenMPの仕様を正確に知るには 規格書を読む必要
OpenMPの仕様情報 httpwwwopenmporgspecifications
最新の規格 OpenMP version 50 (v30 日本語訳あり)
コンパイラの対応状況
◼ GNU 44 v30準拠 47 v31準拠 491 v40準拠
◼ Intel 120以降 v31準拠 170以降 v45準拠
◼ LLVM Clang 60 v31準拠 Clang 70 v45準拠◼ 参考ページ httpswwwopenmporgresourcesopenmp-compilers-tools
◼ 各種情報は随時変更する可能性があります
本資料の主要部 v25までカバー
◼ 「最も基本的」な部分 (まず最初に習得すべき内容) を選択◼ 本講習の範囲外の話題例 タスク並列 ベクトル化 (SIMD) GPU対応
並列実行領域(parallel構文)17
parallel構文
Fortran $omp parallel [指示節 [[] 指示節]]
CC++ pragma parallel [指示節 [[] 指示節]]指示節 (clause) は必要に応じて指定
例 $omp parallel
$omp parallel shared(A)
$omp parallel default (shared) private(i)
printf (ldquoHello OpenMP worldyennrdquo)
pragma omp parallel
printf (ldquoHello OpenMP worldyennrdquo)
write () lsquoHello OpenMP
$omp parallel
write () lsquoHello OpenMP worldrsquo
$omp end parallel
parallelとend parallelで挟まれた部分= 並列実行領域 (parallel region)
parallel直下のブロック文 ( hellip)
= 並列実行領域 (parallel region)
備考 clauseの和訳あれこれ指示節 指示句 節
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
並列処理の形態を説明します
並列処理5
並列処理とは6
処理を分割して同時並列に実行すること
処理終了までの時間の短縮が目的
マルチコア環境では各コアに処理を分配したい
逐次
時間
処理
処理
処理
処理
4並列
処理 処理 処理 処理
プロセスとスレッド7
スレッド
プロセス (実行中のプログラム) の中に含まれるより小さい実行単位(処理の分割単位)
1プロセス内のスレッドはメモリ空間を共有する
1つのスレッドは1つのコアで実行される
逐次実行中(通常)のプロセス(シングルスレッド)
メモリ空間
スレッド
プロセス
メモリ空間
並列実行中のプロセス(2スレッド)
プロセス
スレッド スレッド
プロセスとスレッド8
スレッド
デュアルコアのCPUで考えると
マルチスレッドのプロセスはマルチコアの性能を引き出せる
1スレッドプロセスを処理中の2コアCPU
2スレッドプロセスを処理中の2コアCPU
稼働中 空き
スレッド
稼働中 稼働中
スレッド スレッド
CPU CPU
コア コア コア コア
おもな並列化方式9
プロセス並列スレッド並列
メモリ空間
メモリ空間
プロセス プロセス
プロセス間通信
メモリ空間
スレッド スレッド
ノード間の並列(分散メモリ並列 ノード内の並列も可)
メモリ空間が別々
ノード内の並列(共有メモリ並列)
メモリ空間は一つ (共通)
MPI(Message Passing Interface)OpenMP自動並列
プロセス
スレッド スレッド
おもな並列化方式(contrsquod)10
ハイブリッド並列
プロセス間通信
メモリ空間
スレッド スレッド
プロセス
メモリ空間
スレッド スレッド
プロセス
ノード ノード
ノード内 OpenMP (または自動並列)
ノード間 MPI
通信量通信回数の削減プロセス当たりのメモリ確保
OpenMPのHello worldプログラムと
ループの並列化を紹介します
OpenMP入門11
OpenMPによる並列化12
並列化したい部分に構文 (Construct) を挿入
OpenMP構文による並列化
備考 Constructの和訳あれこれ構文 指示文
do i = 1 4000A(i) = B(i) + C(i)
end do
$omp parallel dodo i = 1 4000
A(i) = B(i) + C(i)end do$omp end parallel do
for (i=0 ilt4000 i++) a[i] = b[i] + b[i]
pragma omp parallel forfor (i=0 ilt4000 i++)
a[i] = b[i] + c[i]
Fortran C
OpenMPによる並列化 (contrsquod)13
並列化したい部分に関数があってもよい (2個の関数を同時実行)
call worker (A)call worker (B)
Fortran C
$omp parallel sections$omp sectioncall worker(A)$omp sectioncall worker(B)
$omp end parallel sections
worker (ampa)worker (ampb)subroutine worker(X)
integer XX = X + 1
end
void worker(int x)
x += 1
pragma omp parallel sections
pragma omp sectionworker (ampa)pragma omp sectionworker (ampb)
代表的なOpenMP構文(Fortran)14
代表的なOpenMP構文(Fortran)
$omp parallel $omp end parallel
$omp do
$omp parallel do
$omp parallel do reduction(+ helliphellip )
$omp sections
$omp critical
$omp barrier
$omp single
代表的なOpenMP構文(CC++)15
代表的なOpenMP構文(CC++)
pragma omp parallel
pragma omp for
pragma omp parallel for
pragma omp parallel for reduction(+ helliphellip )
pragma omp sections
pragma omp critical
pragma omp barrier
pragma omp single
OpenMPの仕様16
OpenMPの仕様を正確に知るには 規格書を読む必要
OpenMPの仕様情報 httpwwwopenmporgspecifications
最新の規格 OpenMP version 50 (v30 日本語訳あり)
コンパイラの対応状況
◼ GNU 44 v30準拠 47 v31準拠 491 v40準拠
◼ Intel 120以降 v31準拠 170以降 v45準拠
◼ LLVM Clang 60 v31準拠 Clang 70 v45準拠◼ 参考ページ httpswwwopenmporgresourcesopenmp-compilers-tools
◼ 各種情報は随時変更する可能性があります
本資料の主要部 v25までカバー
◼ 「最も基本的」な部分 (まず最初に習得すべき内容) を選択◼ 本講習の範囲外の話題例 タスク並列 ベクトル化 (SIMD) GPU対応
並列実行領域(parallel構文)17
parallel構文
Fortran $omp parallel [指示節 [[] 指示節]]
CC++ pragma parallel [指示節 [[] 指示節]]指示節 (clause) は必要に応じて指定
例 $omp parallel
$omp parallel shared(A)
$omp parallel default (shared) private(i)
printf (ldquoHello OpenMP worldyennrdquo)
pragma omp parallel
printf (ldquoHello OpenMP worldyennrdquo)
write () lsquoHello OpenMP
$omp parallel
write () lsquoHello OpenMP worldrsquo
$omp end parallel
parallelとend parallelで挟まれた部分= 並列実行領域 (parallel region)
parallel直下のブロック文 ( hellip)
= 並列実行領域 (parallel region)
備考 clauseの和訳あれこれ指示節 指示句 節
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
並列処理とは6
処理を分割して同時並列に実行すること
処理終了までの時間の短縮が目的
マルチコア環境では各コアに処理を分配したい
逐次
時間
処理
処理
処理
処理
4並列
処理 処理 処理 処理
プロセスとスレッド7
スレッド
プロセス (実行中のプログラム) の中に含まれるより小さい実行単位(処理の分割単位)
1プロセス内のスレッドはメモリ空間を共有する
1つのスレッドは1つのコアで実行される
逐次実行中(通常)のプロセス(シングルスレッド)
メモリ空間
スレッド
プロセス
メモリ空間
並列実行中のプロセス(2スレッド)
プロセス
スレッド スレッド
プロセスとスレッド8
スレッド
デュアルコアのCPUで考えると
マルチスレッドのプロセスはマルチコアの性能を引き出せる
1スレッドプロセスを処理中の2コアCPU
2スレッドプロセスを処理中の2コアCPU
稼働中 空き
スレッド
稼働中 稼働中
スレッド スレッド
CPU CPU
コア コア コア コア
おもな並列化方式9
プロセス並列スレッド並列
メモリ空間
メモリ空間
プロセス プロセス
プロセス間通信
メモリ空間
スレッド スレッド
ノード間の並列(分散メモリ並列 ノード内の並列も可)
メモリ空間が別々
ノード内の並列(共有メモリ並列)
メモリ空間は一つ (共通)
MPI(Message Passing Interface)OpenMP自動並列
プロセス
スレッド スレッド
おもな並列化方式(contrsquod)10
ハイブリッド並列
プロセス間通信
メモリ空間
スレッド スレッド
プロセス
メモリ空間
スレッド スレッド
プロセス
ノード ノード
ノード内 OpenMP (または自動並列)
ノード間 MPI
通信量通信回数の削減プロセス当たりのメモリ確保
OpenMPのHello worldプログラムと
ループの並列化を紹介します
OpenMP入門11
OpenMPによる並列化12
並列化したい部分に構文 (Construct) を挿入
OpenMP構文による並列化
備考 Constructの和訳あれこれ構文 指示文
do i = 1 4000A(i) = B(i) + C(i)
end do
$omp parallel dodo i = 1 4000
A(i) = B(i) + C(i)end do$omp end parallel do
for (i=0 ilt4000 i++) a[i] = b[i] + b[i]
pragma omp parallel forfor (i=0 ilt4000 i++)
a[i] = b[i] + c[i]
Fortran C
OpenMPによる並列化 (contrsquod)13
並列化したい部分に関数があってもよい (2個の関数を同時実行)
call worker (A)call worker (B)
Fortran C
$omp parallel sections$omp sectioncall worker(A)$omp sectioncall worker(B)
$omp end parallel sections
worker (ampa)worker (ampb)subroutine worker(X)
integer XX = X + 1
end
void worker(int x)
x += 1
pragma omp parallel sections
pragma omp sectionworker (ampa)pragma omp sectionworker (ampb)
代表的なOpenMP構文(Fortran)14
代表的なOpenMP構文(Fortran)
$omp parallel $omp end parallel
$omp do
$omp parallel do
$omp parallel do reduction(+ helliphellip )
$omp sections
$omp critical
$omp barrier
$omp single
代表的なOpenMP構文(CC++)15
代表的なOpenMP構文(CC++)
pragma omp parallel
pragma omp for
pragma omp parallel for
pragma omp parallel for reduction(+ helliphellip )
pragma omp sections
pragma omp critical
pragma omp barrier
pragma omp single
OpenMPの仕様16
OpenMPの仕様を正確に知るには 規格書を読む必要
OpenMPの仕様情報 httpwwwopenmporgspecifications
最新の規格 OpenMP version 50 (v30 日本語訳あり)
コンパイラの対応状況
◼ GNU 44 v30準拠 47 v31準拠 491 v40準拠
◼ Intel 120以降 v31準拠 170以降 v45準拠
◼ LLVM Clang 60 v31準拠 Clang 70 v45準拠◼ 参考ページ httpswwwopenmporgresourcesopenmp-compilers-tools
◼ 各種情報は随時変更する可能性があります
本資料の主要部 v25までカバー
◼ 「最も基本的」な部分 (まず最初に習得すべき内容) を選択◼ 本講習の範囲外の話題例 タスク並列 ベクトル化 (SIMD) GPU対応
並列実行領域(parallel構文)17
parallel構文
Fortran $omp parallel [指示節 [[] 指示節]]
CC++ pragma parallel [指示節 [[] 指示節]]指示節 (clause) は必要に応じて指定
例 $omp parallel
$omp parallel shared(A)
$omp parallel default (shared) private(i)
printf (ldquoHello OpenMP worldyennrdquo)
pragma omp parallel
printf (ldquoHello OpenMP worldyennrdquo)
write () lsquoHello OpenMP
$omp parallel
write () lsquoHello OpenMP worldrsquo
$omp end parallel
parallelとend parallelで挟まれた部分= 並列実行領域 (parallel region)
parallel直下のブロック文 ( hellip)
= 並列実行領域 (parallel region)
備考 clauseの和訳あれこれ指示節 指示句 節
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
プロセスとスレッド7
スレッド
プロセス (実行中のプログラム) の中に含まれるより小さい実行単位(処理の分割単位)
1プロセス内のスレッドはメモリ空間を共有する
1つのスレッドは1つのコアで実行される
逐次実行中(通常)のプロセス(シングルスレッド)
メモリ空間
スレッド
プロセス
メモリ空間
並列実行中のプロセス(2スレッド)
プロセス
スレッド スレッド
プロセスとスレッド8
スレッド
デュアルコアのCPUで考えると
マルチスレッドのプロセスはマルチコアの性能を引き出せる
1スレッドプロセスを処理中の2コアCPU
2スレッドプロセスを処理中の2コアCPU
稼働中 空き
スレッド
稼働中 稼働中
スレッド スレッド
CPU CPU
コア コア コア コア
おもな並列化方式9
プロセス並列スレッド並列
メモリ空間
メモリ空間
プロセス プロセス
プロセス間通信
メモリ空間
スレッド スレッド
ノード間の並列(分散メモリ並列 ノード内の並列も可)
メモリ空間が別々
ノード内の並列(共有メモリ並列)
メモリ空間は一つ (共通)
MPI(Message Passing Interface)OpenMP自動並列
プロセス
スレッド スレッド
おもな並列化方式(contrsquod)10
ハイブリッド並列
プロセス間通信
メモリ空間
スレッド スレッド
プロセス
メモリ空間
スレッド スレッド
プロセス
ノード ノード
ノード内 OpenMP (または自動並列)
ノード間 MPI
通信量通信回数の削減プロセス当たりのメモリ確保
OpenMPのHello worldプログラムと
ループの並列化を紹介します
OpenMP入門11
OpenMPによる並列化12
並列化したい部分に構文 (Construct) を挿入
OpenMP構文による並列化
備考 Constructの和訳あれこれ構文 指示文
do i = 1 4000A(i) = B(i) + C(i)
end do
$omp parallel dodo i = 1 4000
A(i) = B(i) + C(i)end do$omp end parallel do
for (i=0 ilt4000 i++) a[i] = b[i] + b[i]
pragma omp parallel forfor (i=0 ilt4000 i++)
a[i] = b[i] + c[i]
Fortran C
OpenMPによる並列化 (contrsquod)13
並列化したい部分に関数があってもよい (2個の関数を同時実行)
call worker (A)call worker (B)
Fortran C
$omp parallel sections$omp sectioncall worker(A)$omp sectioncall worker(B)
$omp end parallel sections
worker (ampa)worker (ampb)subroutine worker(X)
integer XX = X + 1
end
void worker(int x)
x += 1
pragma omp parallel sections
pragma omp sectionworker (ampa)pragma omp sectionworker (ampb)
代表的なOpenMP構文(Fortran)14
代表的なOpenMP構文(Fortran)
$omp parallel $omp end parallel
$omp do
$omp parallel do
$omp parallel do reduction(+ helliphellip )
$omp sections
$omp critical
$omp barrier
$omp single
代表的なOpenMP構文(CC++)15
代表的なOpenMP構文(CC++)
pragma omp parallel
pragma omp for
pragma omp parallel for
pragma omp parallel for reduction(+ helliphellip )
pragma omp sections
pragma omp critical
pragma omp barrier
pragma omp single
OpenMPの仕様16
OpenMPの仕様を正確に知るには 規格書を読む必要
OpenMPの仕様情報 httpwwwopenmporgspecifications
最新の規格 OpenMP version 50 (v30 日本語訳あり)
コンパイラの対応状況
◼ GNU 44 v30準拠 47 v31準拠 491 v40準拠
◼ Intel 120以降 v31準拠 170以降 v45準拠
◼ LLVM Clang 60 v31準拠 Clang 70 v45準拠◼ 参考ページ httpswwwopenmporgresourcesopenmp-compilers-tools
◼ 各種情報は随時変更する可能性があります
本資料の主要部 v25までカバー
◼ 「最も基本的」な部分 (まず最初に習得すべき内容) を選択◼ 本講習の範囲外の話題例 タスク並列 ベクトル化 (SIMD) GPU対応
並列実行領域(parallel構文)17
parallel構文
Fortran $omp parallel [指示節 [[] 指示節]]
CC++ pragma parallel [指示節 [[] 指示節]]指示節 (clause) は必要に応じて指定
例 $omp parallel
$omp parallel shared(A)
$omp parallel default (shared) private(i)
printf (ldquoHello OpenMP worldyennrdquo)
pragma omp parallel
printf (ldquoHello OpenMP worldyennrdquo)
write () lsquoHello OpenMP
$omp parallel
write () lsquoHello OpenMP worldrsquo
$omp end parallel
parallelとend parallelで挟まれた部分= 並列実行領域 (parallel region)
parallel直下のブロック文 ( hellip)
= 並列実行領域 (parallel region)
備考 clauseの和訳あれこれ指示節 指示句 節
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
プロセスとスレッド8
スレッド
デュアルコアのCPUで考えると
マルチスレッドのプロセスはマルチコアの性能を引き出せる
1スレッドプロセスを処理中の2コアCPU
2スレッドプロセスを処理中の2コアCPU
稼働中 空き
スレッド
稼働中 稼働中
スレッド スレッド
CPU CPU
コア コア コア コア
おもな並列化方式9
プロセス並列スレッド並列
メモリ空間
メモリ空間
プロセス プロセス
プロセス間通信
メモリ空間
スレッド スレッド
ノード間の並列(分散メモリ並列 ノード内の並列も可)
メモリ空間が別々
ノード内の並列(共有メモリ並列)
メモリ空間は一つ (共通)
MPI(Message Passing Interface)OpenMP自動並列
プロセス
スレッド スレッド
おもな並列化方式(contrsquod)10
ハイブリッド並列
プロセス間通信
メモリ空間
スレッド スレッド
プロセス
メモリ空間
スレッド スレッド
プロセス
ノード ノード
ノード内 OpenMP (または自動並列)
ノード間 MPI
通信量通信回数の削減プロセス当たりのメモリ確保
OpenMPのHello worldプログラムと
ループの並列化を紹介します
OpenMP入門11
OpenMPによる並列化12
並列化したい部分に構文 (Construct) を挿入
OpenMP構文による並列化
備考 Constructの和訳あれこれ構文 指示文
do i = 1 4000A(i) = B(i) + C(i)
end do
$omp parallel dodo i = 1 4000
A(i) = B(i) + C(i)end do$omp end parallel do
for (i=0 ilt4000 i++) a[i] = b[i] + b[i]
pragma omp parallel forfor (i=0 ilt4000 i++)
a[i] = b[i] + c[i]
Fortran C
OpenMPによる並列化 (contrsquod)13
並列化したい部分に関数があってもよい (2個の関数を同時実行)
call worker (A)call worker (B)
Fortran C
$omp parallel sections$omp sectioncall worker(A)$omp sectioncall worker(B)
$omp end parallel sections
worker (ampa)worker (ampb)subroutine worker(X)
integer XX = X + 1
end
void worker(int x)
x += 1
pragma omp parallel sections
pragma omp sectionworker (ampa)pragma omp sectionworker (ampb)
代表的なOpenMP構文(Fortran)14
代表的なOpenMP構文(Fortran)
$omp parallel $omp end parallel
$omp do
$omp parallel do
$omp parallel do reduction(+ helliphellip )
$omp sections
$omp critical
$omp barrier
$omp single
代表的なOpenMP構文(CC++)15
代表的なOpenMP構文(CC++)
pragma omp parallel
pragma omp for
pragma omp parallel for
pragma omp parallel for reduction(+ helliphellip )
pragma omp sections
pragma omp critical
pragma omp barrier
pragma omp single
OpenMPの仕様16
OpenMPの仕様を正確に知るには 規格書を読む必要
OpenMPの仕様情報 httpwwwopenmporgspecifications
最新の規格 OpenMP version 50 (v30 日本語訳あり)
コンパイラの対応状況
◼ GNU 44 v30準拠 47 v31準拠 491 v40準拠
◼ Intel 120以降 v31準拠 170以降 v45準拠
◼ LLVM Clang 60 v31準拠 Clang 70 v45準拠◼ 参考ページ httpswwwopenmporgresourcesopenmp-compilers-tools
◼ 各種情報は随時変更する可能性があります
本資料の主要部 v25までカバー
◼ 「最も基本的」な部分 (まず最初に習得すべき内容) を選択◼ 本講習の範囲外の話題例 タスク並列 ベクトル化 (SIMD) GPU対応
並列実行領域(parallel構文)17
parallel構文
Fortran $omp parallel [指示節 [[] 指示節]]
CC++ pragma parallel [指示節 [[] 指示節]]指示節 (clause) は必要に応じて指定
例 $omp parallel
$omp parallel shared(A)
$omp parallel default (shared) private(i)
printf (ldquoHello OpenMP worldyennrdquo)
pragma omp parallel
printf (ldquoHello OpenMP worldyennrdquo)
write () lsquoHello OpenMP
$omp parallel
write () lsquoHello OpenMP worldrsquo
$omp end parallel
parallelとend parallelで挟まれた部分= 並列実行領域 (parallel region)
parallel直下のブロック文 ( hellip)
= 並列実行領域 (parallel region)
備考 clauseの和訳あれこれ指示節 指示句 節
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
おもな並列化方式9
プロセス並列スレッド並列
メモリ空間
メモリ空間
プロセス プロセス
プロセス間通信
メモリ空間
スレッド スレッド
ノード間の並列(分散メモリ並列 ノード内の並列も可)
メモリ空間が別々
ノード内の並列(共有メモリ並列)
メモリ空間は一つ (共通)
MPI(Message Passing Interface)OpenMP自動並列
プロセス
スレッド スレッド
おもな並列化方式(contrsquod)10
ハイブリッド並列
プロセス間通信
メモリ空間
スレッド スレッド
プロセス
メモリ空間
スレッド スレッド
プロセス
ノード ノード
ノード内 OpenMP (または自動並列)
ノード間 MPI
通信量通信回数の削減プロセス当たりのメモリ確保
OpenMPのHello worldプログラムと
ループの並列化を紹介します
OpenMP入門11
OpenMPによる並列化12
並列化したい部分に構文 (Construct) を挿入
OpenMP構文による並列化
備考 Constructの和訳あれこれ構文 指示文
do i = 1 4000A(i) = B(i) + C(i)
end do
$omp parallel dodo i = 1 4000
A(i) = B(i) + C(i)end do$omp end parallel do
for (i=0 ilt4000 i++) a[i] = b[i] + b[i]
pragma omp parallel forfor (i=0 ilt4000 i++)
a[i] = b[i] + c[i]
Fortran C
OpenMPによる並列化 (contrsquod)13
並列化したい部分に関数があってもよい (2個の関数を同時実行)
call worker (A)call worker (B)
Fortran C
$omp parallel sections$omp sectioncall worker(A)$omp sectioncall worker(B)
$omp end parallel sections
worker (ampa)worker (ampb)subroutine worker(X)
integer XX = X + 1
end
void worker(int x)
x += 1
pragma omp parallel sections
pragma omp sectionworker (ampa)pragma omp sectionworker (ampb)
代表的なOpenMP構文(Fortran)14
代表的なOpenMP構文(Fortran)
$omp parallel $omp end parallel
$omp do
$omp parallel do
$omp parallel do reduction(+ helliphellip )
$omp sections
$omp critical
$omp barrier
$omp single
代表的なOpenMP構文(CC++)15
代表的なOpenMP構文(CC++)
pragma omp parallel
pragma omp for
pragma omp parallel for
pragma omp parallel for reduction(+ helliphellip )
pragma omp sections
pragma omp critical
pragma omp barrier
pragma omp single
OpenMPの仕様16
OpenMPの仕様を正確に知るには 規格書を読む必要
OpenMPの仕様情報 httpwwwopenmporgspecifications
最新の規格 OpenMP version 50 (v30 日本語訳あり)
コンパイラの対応状況
◼ GNU 44 v30準拠 47 v31準拠 491 v40準拠
◼ Intel 120以降 v31準拠 170以降 v45準拠
◼ LLVM Clang 60 v31準拠 Clang 70 v45準拠◼ 参考ページ httpswwwopenmporgresourcesopenmp-compilers-tools
◼ 各種情報は随時変更する可能性があります
本資料の主要部 v25までカバー
◼ 「最も基本的」な部分 (まず最初に習得すべき内容) を選択◼ 本講習の範囲外の話題例 タスク並列 ベクトル化 (SIMD) GPU対応
並列実行領域(parallel構文)17
parallel構文
Fortran $omp parallel [指示節 [[] 指示節]]
CC++ pragma parallel [指示節 [[] 指示節]]指示節 (clause) は必要に応じて指定
例 $omp parallel
$omp parallel shared(A)
$omp parallel default (shared) private(i)
printf (ldquoHello OpenMP worldyennrdquo)
pragma omp parallel
printf (ldquoHello OpenMP worldyennrdquo)
write () lsquoHello OpenMP
$omp parallel
write () lsquoHello OpenMP worldrsquo
$omp end parallel
parallelとend parallelで挟まれた部分= 並列実行領域 (parallel region)
parallel直下のブロック文 ( hellip)
= 並列実行領域 (parallel region)
備考 clauseの和訳あれこれ指示節 指示句 節
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
おもな並列化方式(contrsquod)10
ハイブリッド並列
プロセス間通信
メモリ空間
スレッド スレッド
プロセス
メモリ空間
スレッド スレッド
プロセス
ノード ノード
ノード内 OpenMP (または自動並列)
ノード間 MPI
通信量通信回数の削減プロセス当たりのメモリ確保
OpenMPのHello worldプログラムと
ループの並列化を紹介します
OpenMP入門11
OpenMPによる並列化12
並列化したい部分に構文 (Construct) を挿入
OpenMP構文による並列化
備考 Constructの和訳あれこれ構文 指示文
do i = 1 4000A(i) = B(i) + C(i)
end do
$omp parallel dodo i = 1 4000
A(i) = B(i) + C(i)end do$omp end parallel do
for (i=0 ilt4000 i++) a[i] = b[i] + b[i]
pragma omp parallel forfor (i=0 ilt4000 i++)
a[i] = b[i] + c[i]
Fortran C
OpenMPによる並列化 (contrsquod)13
並列化したい部分に関数があってもよい (2個の関数を同時実行)
call worker (A)call worker (B)
Fortran C
$omp parallel sections$omp sectioncall worker(A)$omp sectioncall worker(B)
$omp end parallel sections
worker (ampa)worker (ampb)subroutine worker(X)
integer XX = X + 1
end
void worker(int x)
x += 1
pragma omp parallel sections
pragma omp sectionworker (ampa)pragma omp sectionworker (ampb)
代表的なOpenMP構文(Fortran)14
代表的なOpenMP構文(Fortran)
$omp parallel $omp end parallel
$omp do
$omp parallel do
$omp parallel do reduction(+ helliphellip )
$omp sections
$omp critical
$omp barrier
$omp single
代表的なOpenMP構文(CC++)15
代表的なOpenMP構文(CC++)
pragma omp parallel
pragma omp for
pragma omp parallel for
pragma omp parallel for reduction(+ helliphellip )
pragma omp sections
pragma omp critical
pragma omp barrier
pragma omp single
OpenMPの仕様16
OpenMPの仕様を正確に知るには 規格書を読む必要
OpenMPの仕様情報 httpwwwopenmporgspecifications
最新の規格 OpenMP version 50 (v30 日本語訳あり)
コンパイラの対応状況
◼ GNU 44 v30準拠 47 v31準拠 491 v40準拠
◼ Intel 120以降 v31準拠 170以降 v45準拠
◼ LLVM Clang 60 v31準拠 Clang 70 v45準拠◼ 参考ページ httpswwwopenmporgresourcesopenmp-compilers-tools
◼ 各種情報は随時変更する可能性があります
本資料の主要部 v25までカバー
◼ 「最も基本的」な部分 (まず最初に習得すべき内容) を選択◼ 本講習の範囲外の話題例 タスク並列 ベクトル化 (SIMD) GPU対応
並列実行領域(parallel構文)17
parallel構文
Fortran $omp parallel [指示節 [[] 指示節]]
CC++ pragma parallel [指示節 [[] 指示節]]指示節 (clause) は必要に応じて指定
例 $omp parallel
$omp parallel shared(A)
$omp parallel default (shared) private(i)
printf (ldquoHello OpenMP worldyennrdquo)
pragma omp parallel
printf (ldquoHello OpenMP worldyennrdquo)
write () lsquoHello OpenMP
$omp parallel
write () lsquoHello OpenMP worldrsquo
$omp end parallel
parallelとend parallelで挟まれた部分= 並列実行領域 (parallel region)
parallel直下のブロック文 ( hellip)
= 並列実行領域 (parallel region)
備考 clauseの和訳あれこれ指示節 指示句 節
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
OpenMPのHello worldプログラムと
ループの並列化を紹介します
OpenMP入門11
OpenMPによる並列化12
並列化したい部分に構文 (Construct) を挿入
OpenMP構文による並列化
備考 Constructの和訳あれこれ構文 指示文
do i = 1 4000A(i) = B(i) + C(i)
end do
$omp parallel dodo i = 1 4000
A(i) = B(i) + C(i)end do$omp end parallel do
for (i=0 ilt4000 i++) a[i] = b[i] + b[i]
pragma omp parallel forfor (i=0 ilt4000 i++)
a[i] = b[i] + c[i]
Fortran C
OpenMPによる並列化 (contrsquod)13
並列化したい部分に関数があってもよい (2個の関数を同時実行)
call worker (A)call worker (B)
Fortran C
$omp parallel sections$omp sectioncall worker(A)$omp sectioncall worker(B)
$omp end parallel sections
worker (ampa)worker (ampb)subroutine worker(X)
integer XX = X + 1
end
void worker(int x)
x += 1
pragma omp parallel sections
pragma omp sectionworker (ampa)pragma omp sectionworker (ampb)
代表的なOpenMP構文(Fortran)14
代表的なOpenMP構文(Fortran)
$omp parallel $omp end parallel
$omp do
$omp parallel do
$omp parallel do reduction(+ helliphellip )
$omp sections
$omp critical
$omp barrier
$omp single
代表的なOpenMP構文(CC++)15
代表的なOpenMP構文(CC++)
pragma omp parallel
pragma omp for
pragma omp parallel for
pragma omp parallel for reduction(+ helliphellip )
pragma omp sections
pragma omp critical
pragma omp barrier
pragma omp single
OpenMPの仕様16
OpenMPの仕様を正確に知るには 規格書を読む必要
OpenMPの仕様情報 httpwwwopenmporgspecifications
最新の規格 OpenMP version 50 (v30 日本語訳あり)
コンパイラの対応状況
◼ GNU 44 v30準拠 47 v31準拠 491 v40準拠
◼ Intel 120以降 v31準拠 170以降 v45準拠
◼ LLVM Clang 60 v31準拠 Clang 70 v45準拠◼ 参考ページ httpswwwopenmporgresourcesopenmp-compilers-tools
◼ 各種情報は随時変更する可能性があります
本資料の主要部 v25までカバー
◼ 「最も基本的」な部分 (まず最初に習得すべき内容) を選択◼ 本講習の範囲外の話題例 タスク並列 ベクトル化 (SIMD) GPU対応
並列実行領域(parallel構文)17
parallel構文
Fortran $omp parallel [指示節 [[] 指示節]]
CC++ pragma parallel [指示節 [[] 指示節]]指示節 (clause) は必要に応じて指定
例 $omp parallel
$omp parallel shared(A)
$omp parallel default (shared) private(i)
printf (ldquoHello OpenMP worldyennrdquo)
pragma omp parallel
printf (ldquoHello OpenMP worldyennrdquo)
write () lsquoHello OpenMP
$omp parallel
write () lsquoHello OpenMP worldrsquo
$omp end parallel
parallelとend parallelで挟まれた部分= 並列実行領域 (parallel region)
parallel直下のブロック文 ( hellip)
= 並列実行領域 (parallel region)
備考 clauseの和訳あれこれ指示節 指示句 節
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
OpenMPによる並列化12
並列化したい部分に構文 (Construct) を挿入
OpenMP構文による並列化
備考 Constructの和訳あれこれ構文 指示文
do i = 1 4000A(i) = B(i) + C(i)
end do
$omp parallel dodo i = 1 4000
A(i) = B(i) + C(i)end do$omp end parallel do
for (i=0 ilt4000 i++) a[i] = b[i] + b[i]
pragma omp parallel forfor (i=0 ilt4000 i++)
a[i] = b[i] + c[i]
Fortran C
OpenMPによる並列化 (contrsquod)13
並列化したい部分に関数があってもよい (2個の関数を同時実行)
call worker (A)call worker (B)
Fortran C
$omp parallel sections$omp sectioncall worker(A)$omp sectioncall worker(B)
$omp end parallel sections
worker (ampa)worker (ampb)subroutine worker(X)
integer XX = X + 1
end
void worker(int x)
x += 1
pragma omp parallel sections
pragma omp sectionworker (ampa)pragma omp sectionworker (ampb)
代表的なOpenMP構文(Fortran)14
代表的なOpenMP構文(Fortran)
$omp parallel $omp end parallel
$omp do
$omp parallel do
$omp parallel do reduction(+ helliphellip )
$omp sections
$omp critical
$omp barrier
$omp single
代表的なOpenMP構文(CC++)15
代表的なOpenMP構文(CC++)
pragma omp parallel
pragma omp for
pragma omp parallel for
pragma omp parallel for reduction(+ helliphellip )
pragma omp sections
pragma omp critical
pragma omp barrier
pragma omp single
OpenMPの仕様16
OpenMPの仕様を正確に知るには 規格書を読む必要
OpenMPの仕様情報 httpwwwopenmporgspecifications
最新の規格 OpenMP version 50 (v30 日本語訳あり)
コンパイラの対応状況
◼ GNU 44 v30準拠 47 v31準拠 491 v40準拠
◼ Intel 120以降 v31準拠 170以降 v45準拠
◼ LLVM Clang 60 v31準拠 Clang 70 v45準拠◼ 参考ページ httpswwwopenmporgresourcesopenmp-compilers-tools
◼ 各種情報は随時変更する可能性があります
本資料の主要部 v25までカバー
◼ 「最も基本的」な部分 (まず最初に習得すべき内容) を選択◼ 本講習の範囲外の話題例 タスク並列 ベクトル化 (SIMD) GPU対応
並列実行領域(parallel構文)17
parallel構文
Fortran $omp parallel [指示節 [[] 指示節]]
CC++ pragma parallel [指示節 [[] 指示節]]指示節 (clause) は必要に応じて指定
例 $omp parallel
$omp parallel shared(A)
$omp parallel default (shared) private(i)
printf (ldquoHello OpenMP worldyennrdquo)
pragma omp parallel
printf (ldquoHello OpenMP worldyennrdquo)
write () lsquoHello OpenMP
$omp parallel
write () lsquoHello OpenMP worldrsquo
$omp end parallel
parallelとend parallelで挟まれた部分= 並列実行領域 (parallel region)
parallel直下のブロック文 ( hellip)
= 並列実行領域 (parallel region)
備考 clauseの和訳あれこれ指示節 指示句 節
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
OpenMPによる並列化 (contrsquod)13
並列化したい部分に関数があってもよい (2個の関数を同時実行)
call worker (A)call worker (B)
Fortran C
$omp parallel sections$omp sectioncall worker(A)$omp sectioncall worker(B)
$omp end parallel sections
worker (ampa)worker (ampb)subroutine worker(X)
integer XX = X + 1
end
void worker(int x)
x += 1
pragma omp parallel sections
pragma omp sectionworker (ampa)pragma omp sectionworker (ampb)
代表的なOpenMP構文(Fortran)14
代表的なOpenMP構文(Fortran)
$omp parallel $omp end parallel
$omp do
$omp parallel do
$omp parallel do reduction(+ helliphellip )
$omp sections
$omp critical
$omp barrier
$omp single
代表的なOpenMP構文(CC++)15
代表的なOpenMP構文(CC++)
pragma omp parallel
pragma omp for
pragma omp parallel for
pragma omp parallel for reduction(+ helliphellip )
pragma omp sections
pragma omp critical
pragma omp barrier
pragma omp single
OpenMPの仕様16
OpenMPの仕様を正確に知るには 規格書を読む必要
OpenMPの仕様情報 httpwwwopenmporgspecifications
最新の規格 OpenMP version 50 (v30 日本語訳あり)
コンパイラの対応状況
◼ GNU 44 v30準拠 47 v31準拠 491 v40準拠
◼ Intel 120以降 v31準拠 170以降 v45準拠
◼ LLVM Clang 60 v31準拠 Clang 70 v45準拠◼ 参考ページ httpswwwopenmporgresourcesopenmp-compilers-tools
◼ 各種情報は随時変更する可能性があります
本資料の主要部 v25までカバー
◼ 「最も基本的」な部分 (まず最初に習得すべき内容) を選択◼ 本講習の範囲外の話題例 タスク並列 ベクトル化 (SIMD) GPU対応
並列実行領域(parallel構文)17
parallel構文
Fortran $omp parallel [指示節 [[] 指示節]]
CC++ pragma parallel [指示節 [[] 指示節]]指示節 (clause) は必要に応じて指定
例 $omp parallel
$omp parallel shared(A)
$omp parallel default (shared) private(i)
printf (ldquoHello OpenMP worldyennrdquo)
pragma omp parallel
printf (ldquoHello OpenMP worldyennrdquo)
write () lsquoHello OpenMP
$omp parallel
write () lsquoHello OpenMP worldrsquo
$omp end parallel
parallelとend parallelで挟まれた部分= 並列実行領域 (parallel region)
parallel直下のブロック文 ( hellip)
= 並列実行領域 (parallel region)
備考 clauseの和訳あれこれ指示節 指示句 節
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
代表的なOpenMP構文(Fortran)14
代表的なOpenMP構文(Fortran)
$omp parallel $omp end parallel
$omp do
$omp parallel do
$omp parallel do reduction(+ helliphellip )
$omp sections
$omp critical
$omp barrier
$omp single
代表的なOpenMP構文(CC++)15
代表的なOpenMP構文(CC++)
pragma omp parallel
pragma omp for
pragma omp parallel for
pragma omp parallel for reduction(+ helliphellip )
pragma omp sections
pragma omp critical
pragma omp barrier
pragma omp single
OpenMPの仕様16
OpenMPの仕様を正確に知るには 規格書を読む必要
OpenMPの仕様情報 httpwwwopenmporgspecifications
最新の規格 OpenMP version 50 (v30 日本語訳あり)
コンパイラの対応状況
◼ GNU 44 v30準拠 47 v31準拠 491 v40準拠
◼ Intel 120以降 v31準拠 170以降 v45準拠
◼ LLVM Clang 60 v31準拠 Clang 70 v45準拠◼ 参考ページ httpswwwopenmporgresourcesopenmp-compilers-tools
◼ 各種情報は随時変更する可能性があります
本資料の主要部 v25までカバー
◼ 「最も基本的」な部分 (まず最初に習得すべき内容) を選択◼ 本講習の範囲外の話題例 タスク並列 ベクトル化 (SIMD) GPU対応
並列実行領域(parallel構文)17
parallel構文
Fortran $omp parallel [指示節 [[] 指示節]]
CC++ pragma parallel [指示節 [[] 指示節]]指示節 (clause) は必要に応じて指定
例 $omp parallel
$omp parallel shared(A)
$omp parallel default (shared) private(i)
printf (ldquoHello OpenMP worldyennrdquo)
pragma omp parallel
printf (ldquoHello OpenMP worldyennrdquo)
write () lsquoHello OpenMP
$omp parallel
write () lsquoHello OpenMP worldrsquo
$omp end parallel
parallelとend parallelで挟まれた部分= 並列実行領域 (parallel region)
parallel直下のブロック文 ( hellip)
= 並列実行領域 (parallel region)
備考 clauseの和訳あれこれ指示節 指示句 節
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
代表的なOpenMP構文(CC++)15
代表的なOpenMP構文(CC++)
pragma omp parallel
pragma omp for
pragma omp parallel for
pragma omp parallel for reduction(+ helliphellip )
pragma omp sections
pragma omp critical
pragma omp barrier
pragma omp single
OpenMPの仕様16
OpenMPの仕様を正確に知るには 規格書を読む必要
OpenMPの仕様情報 httpwwwopenmporgspecifications
最新の規格 OpenMP version 50 (v30 日本語訳あり)
コンパイラの対応状況
◼ GNU 44 v30準拠 47 v31準拠 491 v40準拠
◼ Intel 120以降 v31準拠 170以降 v45準拠
◼ LLVM Clang 60 v31準拠 Clang 70 v45準拠◼ 参考ページ httpswwwopenmporgresourcesopenmp-compilers-tools
◼ 各種情報は随時変更する可能性があります
本資料の主要部 v25までカバー
◼ 「最も基本的」な部分 (まず最初に習得すべき内容) を選択◼ 本講習の範囲外の話題例 タスク並列 ベクトル化 (SIMD) GPU対応
並列実行領域(parallel構文)17
parallel構文
Fortran $omp parallel [指示節 [[] 指示節]]
CC++ pragma parallel [指示節 [[] 指示節]]指示節 (clause) は必要に応じて指定
例 $omp parallel
$omp parallel shared(A)
$omp parallel default (shared) private(i)
printf (ldquoHello OpenMP worldyennrdquo)
pragma omp parallel
printf (ldquoHello OpenMP worldyennrdquo)
write () lsquoHello OpenMP
$omp parallel
write () lsquoHello OpenMP worldrsquo
$omp end parallel
parallelとend parallelで挟まれた部分= 並列実行領域 (parallel region)
parallel直下のブロック文 ( hellip)
= 並列実行領域 (parallel region)
備考 clauseの和訳あれこれ指示節 指示句 節
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
OpenMPの仕様16
OpenMPの仕様を正確に知るには 規格書を読む必要
OpenMPの仕様情報 httpwwwopenmporgspecifications
最新の規格 OpenMP version 50 (v30 日本語訳あり)
コンパイラの対応状況
◼ GNU 44 v30準拠 47 v31準拠 491 v40準拠
◼ Intel 120以降 v31準拠 170以降 v45準拠
◼ LLVM Clang 60 v31準拠 Clang 70 v45準拠◼ 参考ページ httpswwwopenmporgresourcesopenmp-compilers-tools
◼ 各種情報は随時変更する可能性があります
本資料の主要部 v25までカバー
◼ 「最も基本的」な部分 (まず最初に習得すべき内容) を選択◼ 本講習の範囲外の話題例 タスク並列 ベクトル化 (SIMD) GPU対応
並列実行領域(parallel構文)17
parallel構文
Fortran $omp parallel [指示節 [[] 指示節]]
CC++ pragma parallel [指示節 [[] 指示節]]指示節 (clause) は必要に応じて指定
例 $omp parallel
$omp parallel shared(A)
$omp parallel default (shared) private(i)
printf (ldquoHello OpenMP worldyennrdquo)
pragma omp parallel
printf (ldquoHello OpenMP worldyennrdquo)
write () lsquoHello OpenMP
$omp parallel
write () lsquoHello OpenMP worldrsquo
$omp end parallel
parallelとend parallelで挟まれた部分= 並列実行領域 (parallel region)
parallel直下のブロック文 ( hellip)
= 並列実行領域 (parallel region)
備考 clauseの和訳あれこれ指示節 指示句 節
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
並列実行領域(parallel構文)17
parallel構文
Fortran $omp parallel [指示節 [[] 指示節]]
CC++ pragma parallel [指示節 [[] 指示節]]指示節 (clause) は必要に応じて指定
例 $omp parallel
$omp parallel shared(A)
$omp parallel default (shared) private(i)
printf (ldquoHello OpenMP worldyennrdquo)
pragma omp parallel
printf (ldquoHello OpenMP worldyennrdquo)
write () lsquoHello OpenMP
$omp parallel
write () lsquoHello OpenMP worldrsquo
$omp end parallel
parallelとend parallelで挟まれた部分= 並列実行領域 (parallel region)
parallel直下のブロック文 ( hellip)
= 並列実行領域 (parallel region)
備考 clauseの和訳あれこれ指示節 指示句 節
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
実行例Hello OpenMP world18
$
() Intel ifort -qopenmp hellof
GNU gfortran -fopenmp hellof
PGI pgfortran -mp hellof
() cshの場合 setenv OMP_NUM_THREADS 4
Hello worldHello OpenMP worldHello OpenMP worldHello OpenMP worldHello OpenMP world$
larr OpenMPオプションをつけてコンパイルgfortran -fopenmp hellof ()
larr スレッド数(並列数)を環境変数で設定
$ export OMP_NUM_THREADS=4 ()
larr 実行$ aout
コンパイラによってオプションが違う
並列実行領域の出力(4並列実行)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
OpenMP スレッドの動作19
$omp parallel
並列実行領域
逐次実行領域
$omp end parallel
逐次実行領域
プログラム開始
マスタースレッド
分岐(fork) スレッドチーム結成(ワーカースレッド生成)
スレッド0=マスタースレッド
スレッド2 スレッド3スレッド1
合流(join) スレッドチーム消滅(ワーカースレッド消滅)
プログラム終了
マスタースレッド
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
Parallel構文の効果20
逐次
処理
処理
処理
処理
処理
処理
処理
処理
時間
並列 parallel構文
スレッドの分岐合流を制御
処理の割り振りはしない
並列化には処理の割り振りが必要rarrworksharing構文を利用
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
Worksharing構文21
parallel構文はスレッドチームの分岐合流を制御する
並列化にはさらに処理の割り振り(ワークシェアリング)が必要
OpenMPではworksharing構文を用いる
worksharing構文の種類
loop構文
◼ doforループを分割実行
single構文
◼ 生成されたスレッドのうち1つのスレッドのみが実行
sections構文
◼ 別々に実行できるような複数の処理それぞれを各スレッドで実行
workshare構文(Fortranのみ)
◼ fortran90以降の配列代入文などを分割実行
本資料ではloop構文を主に扱います
v45までの言い方v50のworksharing-loop構文に対応
single構文とsections構文については補足を参照
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
DOループのワークシェアリング22
loop構文
並列実行領域においてdoループを分割しチーム内の各スレッドに割り当てます
デフォルトでは均等に分割され各スレッドにより実行されます
do i = 1 4000V(i) = X(i) + Y(i)
end do
ループ長 n=4000
n=40001 2 3
逐次
1000 2000 3000 4000
スレッド0が実行 スレッド1が実行 スレッド2が実行 スレッド3が実行4並列
4並列で実行
$omp parallel
$omp do
$omp end do
$omp end parallel
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
DOループのワークシェアリングの書式
23
do i = 1 4000
V(i) = X(i) + Y(i)
end doこのDOループを並列化する
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
$omp parallel
$omp do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end do
$omp end parallel
parallel構文$omp parallel [指示節 [ [] 指示節]]
並列実行領域$omp end parallel (省略不可)parallel hellip end parallelで囲まれた領域を並列実行
loop構文$omp do [指示節 [ [] 指示節]]
doループ[$omp end do] (省略可能)
parallel loop構文(parallelとloopの複合構文)
$omp parallel do [指示節 [ [] 指示節]]
doループ
[$omp end parallel do] (省略可能)
後続のdoループを各スレッドで分割して並列実行
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
forループのワークシェアリングの書式
24
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
このforループを並列化する
pragma omp parallel for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
pragma omp parallel
pragma omp for
for ( i=0 ilt4000 i++ )
V[i] = X[i] + Y[i]
parallel 構文(CC++)
pragma omp parallel [指示節 [ [] 指示節]]
後続の領域を並列実行
loop構文(CC++)
pragma omp for [指示節 [ [] 指示節]]
後続のforループを分割して各スレッドに割り当て
parallel loop構文(CC++)
pragma omp parallel for [指示節 [ [] 指示節]]
後続のforループを各スレッドで分割して並列実行
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
スレッド間でのデータの共有属性
(shared属性とprivate属性)
並列実行領域中のデータの属性25
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
データ共有属性(並列実行領域内の変数の属性)
sharedデータ 全てのスレッドからアクセス可能なデータ
privateデータ 各スレッド固有の他のスレッドからは見えないデータ
並列実行領域中のデータの属性26
$omp parallel do
do i = 1 4000
V(i) = X(i) + Y(i)
end do
$omp end parallel do
privateデータ
privateデータ
sharedデータ
V X Yi i
スレッド スレッド
pragma omp parallel for
for (i=0 ilt4000i++)
V[i] = X[i] + Y[i]
bull OpenMPではデータ共有属性をプログラマの責任で設定する必要があるbull 誤った設定は不正な結果(バグ)の原因となり得る
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
データ共有属性の宣言 (shared指示節 private指示節)
典型的にはparallel構文やloop構文の指示節として指定します
データ共有属性の宣言27
$omp parallel do private(i) shared(V X Y)
do i = 1 4000
V( i ) = X( i ) + Y( i )
end do
$omp end parallel do
pragma omp parallel for private(i) shared(V X Y)
for(i = 0 i lt4000 i ++)
V[ i ] = X[ i ] + Y[ i ]
備考 構文内で参照される変数 (Variables referenced in a Construct) に関する暗黙のデータ共有属性 rarr この例では「private(i) shared(V X Y)」を省略可 (暗黙に決定)
bull 並列実行領域において指定の無いほとんどのデータは shared属性bull ループ内のループインデックス変数は private属性
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
Shared属性28
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yshared(V X Y)
並列実行領域
sharedデータ
すべてのスレッドから参照可能
並列実行領域開始前と同一の(メモリ領域に記憶される)変数
shared指示節で指定されたデータあるいは暗黙のshared属性データ
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
Shared属性 (contrsquod)29
デフォルトでshared属性となる(覚えておくべき) 例
並列実行領域の前でメモリが確保された変数
動的に確保した配列
◼ Fortran allocate
◼ C malloc C++ new
「どこからでも」参照可能 (global scope) な変数
◼ Fortran module内の変数 (commonブロックの変数)
◼ CC++ ファイルスコープをもつ変数
「生存期間」がプログラム終了までの変数
◼ Fortran 関数内でsave属性付きの変数
◼ CC++ 関数内でstatic付きで宣言された変数
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
Private属性30
privateデータ
各スレッドに固有のデータ (異なるスレッドから参照不可)
並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ
初期値は未定義
プログラム開始
マスタースレッド0
マスタースレッド0 スレッド3スレッド1 スレッド2
V X Yi
private( i )i0 i1 i2 i3
互いにアクセスすることはできない
private指示節で指定されたデータ
例えば i0はスレッド0に固有の i を表す
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
Private属性 (contrsquod)31
デフォルトでprivate属性となる(覚えておくべき)例
ループのインデックス
並列実行領域内で呼び出される関数で「局所的に」使われる変数
◼ 関数における値渡し仮引数 (例 Cの関数の仮引数)
integer X(4)
(hellip)
$omp parallel
$omp do
do i = 1 4
X(i) = i
call worker(X( i ) )
end do
$omp do
$omp end parallel
subroutine worker( y )
integer intent(inout) y
integer save nn
integer u
(hellip)
int X[4]
(hellip)
pragma omp parallel
pragma omp for
for( i =0 i lt 4 i++)
X[ i ] = i
worker(ampX[ i ] )
void worker(int y )
static int nn
int u
(hellip)
演習 private属性を有する変数はどれか
cf 関数における参照渡し仮引数並列実行領域の呼び出し元(実引数)の属性を引き継ぐ (v45)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
Firstprivate属性32
firstprivate指示節で指定されたデータ
private属性
ただし 並列実行領域に入る直前の値で各スレッドの値が初期化
プログラム開始
マスタースレッド0
firstprivate( t )
マスタースレッド0
t0= 20スレッド1
t1= 20スレッド2
t2= 20スレッド3
t3= 20
t= 20 V X Y
t = 20
$omp parallel do firstprivate(t)
do i = 1 4000
if ( i gt nmax) t = 00
V(i) = X(i) + t Y(i)
end do
$omp end parallel do
t = 20
pragma omp parallel for firstprivate(t)
for ( i = 0 i lt 4000 i++ )
if ( i gt nmax)
t = 00
V(i) = X(i) + t Y(i)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
データ共有属性の指定33
デフォルトの属性から変更する必要があるとき使用
shared指示節 shared ( [変数のリスト])
private指示節 private ( [変数のリスト])
firstprivate指示節 firstprivate ([変数のリスト] )
default指示節 構文内で参照される変数を
◼ default (shared) rarr 全てshared属性と設定
◼ default (private) rarr 全てprivate属性と設定
◼ default (none) rarr 全て「個別に指示節で指定する必要」がある設定指定漏れがある場合はコンパイルエラー
◼ 「バグ取りに有用」とよく言われる設定
その他 (補足を参照) lastprivate指示節 threadprivate構文
どの構文でどの指示節が指定可能か rarr 仕様書で確認
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
データ共有属性の設定ミス rarr アクセス競合
アクセス競合に注意すべきループ〜データ共有属性ミスの例〜
34
備考bull ここからはFortranのコード例に基づき説明を行いますbull 本質的な部分はCC++についても同じです
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
一時変数を含むループ35
private属性の指定忘れに注意$omp parallel do
do i = 1 4t = X(i) + Y(i)V(i) = V(i) + t t
end do
スレッド0
同時更新
t をprivate属性指定しないと t はそれぞれのスレッドから同時更新されタイミングによって結果が異なってしまいます
rArr $omp parallel do private( t )
t
スレッド1
t = X(1) + Y(1)V(1) = V(1) + t t
t = X(2) + Y(2)V(2) = V(2) + t t
t = X(3) + Y(3)V(3) = V(3) + t t
t = X(4) + Y(4)V(4) = V(4) + t t
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
一時変数を含むループ(cont)36
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
V X Yt
darrt
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新t =hellip
t tスレッド0が結果を読込
スレッド1が更新
スレッド1が結果を読込
プログラマが期待した動作
各スレッドの更新rarr読込に運良く重なりが無ければ正しい結果となる
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
一時変数を含むループ(cont)37
private属性指定の見落とし
-右の例ではループインデックス i 以外
すべてshared属性となってしまう
shared(t V X Y)
$omp parallel dodo i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end doマスタースレッド0
スレッド0が更新
別スレッドの意図せぬ更新
スレッド0が不正な結果を読込
異常終了せず常に不正な値を与えるわけではないので表面化しにくい
意図しないタイミングでのtの更新が発生する可能性があり時々不正な結果となる
t =hellip
t t
V X Yt
darrt
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
一時変数を含むループ(修正版)38
プログラム開始
マスタースレッド0
private( t )
shared(V X Y)
$omp parallel do private( t )do i = 1 4000
t = X(i) + Y(i)V(i) = V(i) + t t
end do
private属性を正しく指定する
-左辺にあって複数のスレッドが更新する変数はprivateにする
V X Y
マスタースレッド0
t0
スレッド1
t1
スレッド2
t2
スレッド3
t3
プログラムは正常に動作します
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
private属性とshared属性の宣言のミスrarrバグの原因
実行の度に結果が異なる場合がある
発生頻度は2回に1度のこともあれば数千回に1回のこともrarr発見しにくい
OpenMPではデータ共有属性を注意して設定する必要がある
OpenMPのバグの特徴39
Rule of Thumb1 default(none)を設定して属性をつけるべき変数を見つける2 代入 (=) の左辺 (ストア) にある変数に注目する特にループインデックスに依存しない変数には
private属性をつけるべきか検討する3 並列実行領域で呼び出される関数中にmodule変数が使われる場合はshared属性で問題ないか
検討する (Fortran)4 並列実行領域で呼び出される関数中にstaticやsaveが現れたら諦める (or 使わないようにプログ
ラムを書き変える)
補足 (スレッドセーフ)を参照
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
並列化してはダメなループ
ループの並列化と依存性40
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
並列化できないループ並列化してはダメなループ
41
ループ構文で並列化できないループ
途中でループを抜ける命令がある
goto ndash continueループ
do-while文 (ループ長をカウントできない)
並列化してはいけないループ(次スライドで説明)
他のサイクルの結果を参照するループ=反復(サイクル)間に依存性のあるループ
◼ OpenMPで並列実行できてしまう
◼ 逐次実行と異なる結果を与えてしまう
$omp parallel dodo i = 1 N
if ( 〜 ) exitA(i) = A(i) + 10
end do
$omp parallel dodo i = 1 N
V(i) = V(i - 1) + aend do
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
並列化してはいけない例(1)フロー依存のあるループ(逐次)
42
do i = 2 7V(i) = V(i - 1) + a
end do
i=2 V(2) = V(1) + a
i=3 V(3) = V(2) + a
i=4 V(4) = V(3) + a
i=5 V(5) = V(4) + a
i=6 V(6) = V(5) + a
i=7 V(7) = V(6) + a
逐次実行
上のサイクルの結果を利用して計算している
例えばi=5の計算
V(5)はサイクルi=4の結果のV(4)を用いて計算する
分割
フロー依存 Read-After-Write
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
並列化してはいけない例(1)フロー依存のあるループ
43
$omp parallel dodo i = 2 7
V(i) = V(i-1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)を計算する前にスレッド1がV(4)を参照してしまいます
V(2) = V(1) + a
V(3) = V(2) + a
V(4) = V(3) + a最後にV(4)の計算が完了
V(5) = V(4) + a
V(6) = V(5) + a
V(7) = V(6) + a
最初に更新後のV(4)の値が必要
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
並列化してはいけない例(2)逆依存のあるループ(逐次)
44
do i = 1 6V(i) = V(i+1) + a
end do
i=1 V(1) = V(2) + a
i=2 V(2) = V(3) + a
i=3 V(3) = V(4) + a
i=4 V(4) = V(5) + a
i=5 V(5) = V(6) + a
i=6 V(6) = V(7) + a
逐次実行
上のサイクルで使用した要素を上書きしながら計算している
例えばi=3の計算 V(3)は更新前のV(4)を用
いて計算し次のi=4でV(4)は上書きされます
分割
逆依存 Write-After-Read
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
並列化してはいけない例(2)逆依存のあるループ
45
$omp parallel dodo i = 1 6
V(i) = V(i+1) + a end do
スレッド0 スレッド1
計算結果が実行順序に
依存
(注)スレッド0がV(4)の元の値を参照とする前にスレッド1が先にV(4)の値を更新してしまいます
最後に更新前のV(4)が必要
V(1) = V(2) + a
V(2) = V(3) + a
V(3) = V(4) + a
V(4) = V(5) + a
V(5) = V(6) + a
V(6) = V(7) + a
最初に V(4)の値を上書き
備考 ベクトル化 (SIMD化)は可能なパターン
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
並列化してはいけない例(3)重なりのある間接参照のあるループ
46
間接参照のあるループ
$omp parallel dodo i = 1 4
V( List( i ) ) = X(i) + Y(i)end do
スレッド0
例えばList(2) = List(4) の場合どちらの間接参照が後にアクセスされるかによって結果が変化します(順番に依存)
rArr 配列List( )の値がすべて異なる(ユニークな)場合を除き並列化してはいけません
V
スレッド1
同じ要素を更新する可能性
V( List(1) ) = X(1) + Y(1)
V( List(2) ) = X(2) + Y(2)
V( List(3) ) = X(3) + Y(3)
V( List(4) ) = X(4) + Y(4)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
総和などの計算をおこなうreduction指示節を
説明します
Reduction演算47
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
総和計算の並列化48
総和の計算を並列化する rArr reduction指示節$omp parallel do
do i = 1 4S = S + V(i)
end do
スレッド0 スレッド1
同時更新の可能性
bull Sがshared属性ならばスレッド0とスレッド1がそれぞれ勝手なタイングでSの値を更新するため結果は不定となります
bull もしSをprivate属性にすると全体の総和を得ることはできません
S
解決するには
S = S + V(1)
S = S + V(2)
S = S + V(3)
S = S + V(4)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
総和計算の並列化(cont)49
総和の計算を並列化する rArr reduction指示節
$omp parallel do reduction (+ S)do i = 1 4
S = S + V(i) end do
スレッド0 スレッド1
S0 = 0
S0 = S0 + V(1)
S0 = S0 + V(2)
S1 = 0
S1 = S1 + V(3)
S1 = S1 + V(4)
(注)Reduction演算は計算の順序が逐次演算と異なります
そのため丸め誤差により結果が微妙に異なる可能性があります
∵数値計算的には V(1) + V(2) + V(3) +V(4) ne V(1) + V(2) + V(3) +V(4)
Sは特殊なprivate変数として扱われる(reduction変数)
初期値
加算順序はスレッド番号順とは限りませんS = S + S0 + S1 元の変数に加算
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
Reduction指示節50
Reduction演算 複数の変数を何らかの演算で一個の変数に縮約する操作
一般例 r = r op expr の繰り返し等(r reduction変数 expr rを参照しない式)
Reduction指示節により ループ内でreduction変数のprivateなコピーを作成し
ループ終了後各スレッドの演算結果を元の変数に縮約する
$omp parallel do reduction(op r1 [ r2] )
op reduction演算に使用する演算組込手続(+ - and or eqv neqv max min iand ior ieor)
(CC++ + - amp | ^ ampamp ||)
r1 [ r2] reduction変数(複数可)
総和(足し算)以外の演算にもreduction指示節が使えます
reduction変数に配列を指定することも可能Fortran OpenMP v20以降 CC++ OpenMP v45以降
OpenMP v31以降 max minも可能
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
Reduction指示節(cont)51
表 Reduction変数の初期値
Reduction変数はループ内ではprivateな一時変数として扱われます
ループ開始時に演算子の種類に応じて適切に初期化されます
演算 初期値 演算 初期値
+ 0 neqv false
1 max変数の型で表せる最小の値
- 0 min変数の型で表せる最大の値
and true iand すべてのビットが 1
or false ior 0
eqv true ieor 0
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
オーバーヘッドとロードバランスそして
OpenMPで用意されているスケジューリングの方法について説明します
オーバヘッドとロードバランス52
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
オーバーヘッド53
並列化によってプログラムの実行時間を短縮することができますが
完全な並列化をおこなってもオーバーヘッドのため14にはならない
時間
逐次 4並列並列化
14
1
オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)
並列化にはオーバーヘッドがつきものですbull スレッド生成同期bull 並列化に伴うコード変更による処理の増加等
オ ー バ ー ヘ ッ ド
オ ー バ ー ヘ ッ ド
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
オーバーヘッド(contrsquod)54
$omp parallel dodo i = 1 10
〜〜〜end do
$omp end parallel do
$omp parallel dodo i = 1 1000000
〜〜〜end do
$omp end parallel do
反復数の少ないループより多いループの方がオーバーヘッドが相対的に小さくなります
$omp parallel dodo j = 1 n
do i = 1 n〜〜〜
end doend do
$omp end parallel do
do j = 1 n$omp parallel do
do i = 1 n〜〜〜
end do$omp end parallel do
end do
内側ループより外側ループを並列化した方が「$omp parallel do」の呼び出し回数が少なくオーバーヘッドが少なくなります
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
ロードバランス55
均等な処理の割り振り(load balancing)にも注意する必要があります
時間
逐次 4並列並列化
14
1
idleidle idle
不均等(インバランス)な割振りでは期待した性能が出ないことがあります
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
ループ構文とスケジューリング56
Schedule指示節によりループ反復の割当方法を変更できます
スケジューリング指示節 割当方法
schedule(static) 均等に分割<デフォルト>
schedule(static chunk) chunkで指定した反復数のチャンクに分割しスレッド番号順に巡回的に割り当てます
schedule(dynamic [ chunk ] ) chunkで指定した反復数のチャンクに分割しスレッドからの要求に応じて動的に割り当てます各スレッドは1チャンクを実行し次のチャンクを要求します<chunk省略時はchunk=1>
schedule(guided [ chunk ] ) dynamicと同様ですがチャンクの大きさが残りの反復数に応じて徐々に小さくなりますチャンク分割サイズはchunkで指定した値が最小になります<chunk省略時はchunk=1>
schedule(auto) スケジューリングはコンパイラおよびまたは実行時システムに委ねられます
schedule(runtime) スケジューリングは実行時の環境変数OMP_SCHEDULEによって決定されます例 export OMP_SCHEDULE=ldquoguided 1rdquo
$omp parallel do schedule(スケジューリングの種別)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
サイズ2のチャンクに分割し順繰りに配分
STATIC(静的) スケジューリング57
Schedule指示節によりループ反復の割当方法を変更できます
schedule(static)
schedule(static 2)
逐次
静的割り当て 実行前に割り当てを決める
全てをマスタースレッドが処理
均等に分割<デフォルト>
サイズ1のチャンクに分割し順繰りに配分schedule(static 1)
デフォルト
$omp parallel do schedule(スケジューリングの種別)
(はループの1反復(1サイクル)を示す)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
schedule(dynamic 2)
動的割り当て 処理の終わったスレッドが次のチャンクに取りかかる
サイズ2のチャンクに分割し動的に割り当て (デフォルトのチャンクサイズは1)
DYNAMIC(動的) スケジューリング58
schedule(guided 2)
チャンクサイズを最初は大きくとり徐々に小さくする
Schedule指示節によりループ反復の割当方法を変更できます
$omp parallel do schedule(スケジューリングの種別)
サイクルごとの処理量が不均一な時に効果的だがstaticスケジューリングよりオーバーヘッドが大きい
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
スケジューリングとロードバランス59
三角行列とロードインバランス
do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
j
i
$omp parallel dodo j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
単純に4並列実行
スレッド 0 1 2 3 が担当
処理量に差rArr ロードインバランス状態
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
スケジューリングとロードバランス(cont)
60
ロードバランスの改善
$omp parallel do schedule(dynamic)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当
チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し負荷が均等になりますDynamicは完璧なように思えますがオーバーヘッドが大きいので注意が必要です
$omp parallel do schedule(static 1)do j = 1 ndo i = j n
A(i j) = A(i j) + B(i j) end doend do
スレッド 0 1 2 3 3 2 1 0 1 0 3 2が担当
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
同期 並列処理の制御61
排他処理
同期
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
暗黙の同期62
ループ構文などのワークシェアリング構文の出口では暗黙に同期処理が行われます
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
各DOループの終了時に全スレッドの処理終了を待ってから次のDOループの処理に移ります待ち合わせのための若干のオーバーヘッドがかかります
1 2 3マスタースレッド0
暗黙の同期
暗黙の同期 待ち合わせ
待ち合わせ
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
nowait指示節63
暗黙の同期処理が(本当に)不要 rarr nowait指示節で同期を回避 rarr 待ち合わせのオーバヘッドの削減
$omp parallel$omp do
do i = 1 nV(i) = V(i) + X(i)
end do$omp end do nowait$omp do
do i = 1 nW(i) = W(i) + Y(i)
end do$omp end do$omp end parallel
1 2 3マスタースレッド0
暗黙の同期
同期を回避
bull 誤った箇所にnowaitを指定すると不正な結果になるbull 絶対に間違えない確信があるときを除き使用しないほうがよい
待ち合わせ
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
バリア同期(barrier構文)64
$omp parallelhelliphellip
$omp masterallocate( V(n) )
$omp end master$omp barrier$omp do
do i = 1 nV(i) = Y(i)
end do$omp end do$omp end parallel
master構文は暗黙の同期(待ち合わせ)を行いませんのでスレッド1〜3の後の処理(緑)を待たせるためにはbarrier指示節が必要です
待ち合わせ
barrier構文- スレッドの待ち合わせ(同期)を行います
暗黙の同期
1 2 3マスタースレッド0
配列Vの準備
待ち合わせ
待ち合わせ
備考 master構文ではマスタースレッドのみが構文内を処理します
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
critical構文(排他制御)65
program get_sumreal sumreal x(4000)helliphellipsum = 00
$omp parallel do shared (x sum)do i = 1 4000
$omp criticalsum = sum + x(i)
$omp end criticalend do
$omp end parallel doend
この例はreductionのマニュアル実装に対応通常は素直にreduction指示節を使うべき
マスタースレッド0
1 2 3
一人ずつアクセスする
critical構文は指定範囲について複数スレッドの処理が重ならないようにし(排他制御)アクセス競合を回避する
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
実行時ライブラリルーチンと環境変数
66
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
実行時ライブラリルーチン67
OpenMPでは種々のルーチンが用意されている
OpenMP API実行時ライブラリルーチン
◼ 並列実行環境の制御や問合せを行う実行環境ルーチン
◼ データへのアクセスを同期して行うためのロックルーチン
◼ 時間計測ルーチン
利用するには
Fortran 次の中でどちらかを設定
◼ include lsquoomp_libhrsquo hArr一般的なFortran
◼ use omp_lib hArr Fortran90モジュールファイル◼ 上記の少なくとも一方が存在することになっている
CC++
◼ include ltomphgt
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
代表的なライブラリルーチン68
ルーチン名 返値(CC++)
内容
omp_get_thread_num integer
(int)このルーチンを呼び出したチームに属するスレッド番号を返します0〜[スレッド数-1]の値を返しますマスタースレッドは0
omp_get_max_threads integer
(int)並列実行領域で利用できるスレッド数の最大値を返します(並列実行領域に入る前でも利用できます)
omp_get_num_threads integer
(int)現在の並列実行領域を実行中のスレッド数を返します
omp_in_parallel logical
(int)活動状態の並列実行領域内から呼び出された場合「true」それ以外は「false」を返します並列区間非並列区間の両方から呼ばれるサブルーチンの分岐に利用できます
omp_get_wtime double
precision
(double)
wall clockの経過時間を秒単位で返します
ルーチン名はFortranとC++で共通
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
環境変数69
環境変数 内容
OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します
OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジューリングを制御します(デフォルトはstatic)
OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定しますスレッドごとの固有データなどのメモリ領域に利用されます
(注)OMP_STACKSIZE大きなprivate属性の配列を用いるプログラムではスレッドのprivate用のスタックサイズが不足する場合がありますそのような場合はこの環境変数を十分大きい値で設定します
OpenMPプログラムの実行に影響する主な環境変数
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
まとめ70
「最も基本的」な部分を説明
ループの並列化がOpenMPを使ってできるようになった
◼ なぜループの並列化が基本か ◼ 科学技術計算で典型的なホットスポットになり得るため
◼ 時間が掛かっている部分(のみ) を削減する チューニングの原則
Next step
性能の評価方法を知る
◼ アムダール則 (strong scaling) とグスタフソン則 (weak scaling)
性能向上につながる可能性があるポイントを知る
◼ スケジューリングの設定によりロードインバランス影響を緩和
◼ False sharing (異なるスレッドが同じキャッシュラインにアクセス) に配慮
◼ NUMAシステムにおけるThread Affinityの制御
ループの並列化以外のOpenMPの機能を知る
◼ タスク並列関連 (sections構文 single構文 task構文)
◼ 並行処理 (simd構文)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
参考文献71
チュートリアル
httpscomputingllnlgovtutorialsopenMP
読みやすい書籍
Timothy G Mattson Yu (Helen) He and Alice E Koniges The OpenMP
Common Core (MIT 2019)
牛島省 「OpenMPによる並列プログラミングと数値計算法」 (丸善2006)
[Fortranユーザ向け]
菅原清文「CC++プログラマーのためのOpenMP並列プログラミング」 (カットシステム 2009) [CC++ユーザ向け]
レファレンスガイド
Ruud van der Pas Eric Stotzer and Christian Terboven Using OpenMP
ndash The Next Step (MIT2017)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
補足72
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
Single構文73
$omp parallelhelliphellip
$omp singlewrite(6 ) lsquoSerial processingrsquo
$omp end singlehelliphellip
$omp end parallel
逐次処理並列実行領域
指定領域内を特定の1スレッドのみが処理
マスタースレッド (スレッド番号=0)とは限らない
warksharing構文のため処理の末端でスレッド間で暗黙の同期処理
並列領域の中で逐次処理をしたい場合に利用
cf master構文 single構文と似たような機能bull マスタースレッド (スレッド番号=0) のみが指定領域を処理bull 指定領域の末端でスレッド間で同期しないbull master構文は(ある種)の排他制御に相当
bull スレッド=0 処理 それ以外 スレッド0で処理する部分を飛ばして先に進む
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
Sections構文74
処理量のバラツキが大きいと並列化の効果が出にくくなります
処理A 処理B 処理C 処理D 処理の流れ
$omp sections$omp section
hellip処理Ahellip$omp section
hellip処理Bhellip$omp section
hellip処理Chellip$omp section
hellip処理Dhellip$omp end sections
helliphellip
処理A
処理B
処理C
処理D
sections構文による並列化
独立な処理ABCDを並列に実行
◼ 処理の中にサブルーチン関数コールがあってもよい
◼ 各スレッドに「バラバラ」に「仕事=タスク」をさせる (タスク並列)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
同期に関する各種構文75
構文 内容
atomic インクリメントなどの特定の演算について排他処理をしますatomic演算をサポートしているプロセッサではcritical
構文を使用するより高速な処理が期待されます
flush フラッシュ操作 (あるスレッドが持つ一時的なビュー(レジスタやキャッシュ等の内容)をメモリの内容と一致させる)
ordered 指定したループ領域において逐次実行した場合と同じ順序で実行するよう順序付けを行います
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
Lastprivate属性76
プログラム開始
マスタースレッド0
lastprivate( i )
$omp parallel do lastprivate(i)do i = 1 4000
V(i) = X(i) + Y(i)end doV(i) = X(i) i = 4001
Lastprivate指示節
-Private変数と同様の属性をもちますが並列実行領域後にループの逐次的な終値に相当する反復後の値が代入されます
V X Y shared(V X Y)
マスタースレッド0
i0i=11000
スレッド1
i1i=10012000
スレッド2
i2i=20013000
スレッド3
i3i=30014000
マスタースレッド0
i=4001i=4000の反復(逐次実行した場合の最終に相当する反復)終了後の値
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
Threadprivate構文77
以下のときthreadprivate構文を使います
複数のルーチンからアクセスする変数(commonブロック変数SAVE変数module変数)がある
かつその変数がスレッドごとに異なる値を持つ必要がある(=shared属性ではtimes)
shared属性
並列実行領域内のすべてのスレッドから
アクセス可能な共有データ
スレッド0 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
スレッド1 threadprivate属性ルーチンAB の共通データ
ルーチンA固有なデータ ルーチンB
固有なデータ
単にprivate属性とすると新たにスタック領域に変数配列が確保され複数のルーチンから共有できなくなってしまいます
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
Threadprivate構文(contrsquod)78
threadprivate構文(commonブロックの例)
commonブロック内の変数をスレッド内で複数のsubroutineからアクセスできる状態のままprivate化できます(スレッドごとに固有の値を持つことができます)
対象とするcommonブロックの宣言の直後に記述します
◼ 対象が複数ある場合はコンマで区切って記述します
◼ 対象が宣言されている全てのプログラム単位(subroutine等)に記述します
commonブロックの要素equivalence文に現れる変数はthreadprivate構文で指定できません
指定されたcommonブロックの変数は並列実行領域の終了後も存在し続け次の並列実行領域でアクセスした時にもデータの内容を保持しています
common com A B$omp threadprivate ( com )
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
Threadprivate構文(contrsquod)79
copyin指示節◼ 並列領域開始時のthreadprivate変数の初期化
マスタースレッド以外のスレッドのthreadprivate変数は自動的に初期化されませんcopyin指示節により並列実行領域の開始時にマスタースレッドのデータの内容を各スレッドにコピーします
copyprivate指示節◼ Single領域(並列領域内の逐次領域)終了後の各スレッドへのデータ転送
single構文の終りにsingle実行スレッドの変数を他のスレッドの対応する変数へデータをブロードキャスト(コピー)します
$omp parallel copyin ( com )
$omp end single copyprivate ( com )
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
Threadprivate構文(contrsquod)80
program routine_Ainclude omp_libhcommon comid
$omp threadprivate ( com )$omp parallel
id = omp_get_thread_num()call routine_B
$omp end parallel
write()2nd parallel region$omp parallel
call routine_B$omp end parallel
end
subroutine routine_Binclude omp_libhcommon comid
$omp threadprivate ( com )write() rsquoid=lsquo idend
Threadprivate変数 id はroutine_A
routine_Bの両方からアクセス可
変数 id の値はスレッドごとに異なる
2つ目の並列実行領域でも各スレッド
ごとの値が保存される
並列実行領域1
並列実行領域2
2つのルーチンが参照するcommonブロックをthreadprivate化する例を示します
メインルーチン 「routine_A」 サブルーチン 「routine_B」
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
スレッドセーフ81
スレッドセーフなルーチンとは
複数のスレッドで同時に実行しても意図した機能を果たすルーチン
◼ 例えば間接参照を含むルーチンで同一のアドレスをアクセスするような処理はアクセス競合が発生しておりスレッドセーフではなくなります
スレッドセーフでない処理の例◼ COMMONやSAVE属性の変数にアクセスしている関数サブルーチンで
排他制御が正しくできていないルーチン
◼ IO処理◼ 本資料では理解しやすさのため並列実行領域内でIO処理を実行するプログラム例が
多数ありますが本来はcritical構文等で排他制御をすべきです
スレッドセーフでない処理を並列実行してしまうと
計算結果が不正だったりプログラムが異常終了
常に発生するとは限らない上実行環境により頻度が変わるため問題が発見しにくい場合があります
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
並列化率とアムダールの法則82
アムダールの法則
◼ オーバーヘッドを無視した理想的な条件でも
◼ 速度向上の上限は逐次部の割合(1-p)で決まってしまいます
時間
逐次
逐次部
infin並列
1-p
p
逐次部
1(1-p)倍が速度向上率の上限
並列化率80 ( p = 08 ) ならば102 = 5倍が上限
予定の並列数にふさわしい並列化率以上である必要があります
並列化済
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
1 2 4 81
2
4
8
並列化率とアムダールの法則(cont)83
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 12
1-p = 14
1-p = 0逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
1-p = 18
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(88)
(75)
(50)
23 29
逐次部の割合
43
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)
1 10 100 1000 104 1051
10
100
1000
104
105
並列化率とアムダールの法則(cont)84
アム
ダー
ル則
によ
る速
度向
上
並列数(N)
1-p = 110
1-p = 1100
1-p = 11000
1-p = 110000
1-p = 0
逐次部の割合(1-p)が1(並列数)以下になるよう並列化を進めましょう
速度向上 s =1
1- p( ) + p N
(p並列化率N並列数)
アムダールの法則
(並列化率100)
(9999)
(999)
(99)
(90)
1-p = 1100000(99999)