並列プログラミング 入門(openmp...2020/02/03  · 並列プログラミング...

84
並列プログラミング 入門(OpenMP編) 高度情報科学技術研究機構 1 登録施設利用促進機関 / 文科省委託事業「HPCI運営」代表機関 一般財団法人 高度情報科学技術研究機構(RIST)

Upload: others

Post on 06-Jun-2020

1 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

並列プログラミング入門(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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 2: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 3: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

はじめに

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 4: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 5: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

並列処理の形態を説明します

並列処理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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 6: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

並列処理とは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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 7: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

プロセスとスレッド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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 8: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

プロセスとスレッド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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 9: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

おもな並列化方式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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 10: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

おもな並列化方式(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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 11: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 12: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 13: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 14: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

代表的な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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 15: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

代表的な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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 16: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 17: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

並列実行領域(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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 18: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

実行例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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 19: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 20: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 21: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 22: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 23: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 24: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 25: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

スレッド間でのデータの共有属性

(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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 26: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

データ共有属性(並列実行領域内の変数の属性)

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 27: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

データ共有属性の宣言 (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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 28: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 29: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 30: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 31: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 32: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 33: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

データ共有属性の指定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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 34: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

データ共有属性の設定ミス 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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 35: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

一時変数を含むループ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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 36: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

一時変数を含むループ(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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 37: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

一時変数を含むループ(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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 38: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

一時変数を含むループ(修正版)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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 39: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 40: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

並列化してはダメなループ

ループの並列化と依存性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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 41: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

並列化できないループ並列化してはダメなループ

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 42: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

並列化してはいけない例(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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 43: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

並列化してはいけない例(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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 44: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

並列化してはいけない例(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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 45: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

並列化してはいけない例(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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 46: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

並列化してはいけない例(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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 47: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

総和などの計算をおこなう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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 48: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

総和計算の並列化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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 49: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

総和計算の並列化(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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 50: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 51: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 52: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

オーバーヘッドとロードバランスそして

OpenMPで用意されているスケジューリングの方法について説明します

オーバヘッドとロードバランス52

オーバーヘッド53

並列化によってプログラムの実行時間を短縮することができますが

完全な並列化をおこなってもオーバーヘッドのため14にはならない

時間

逐次 4並列並列化

14

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 53: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

オーバーヘッド53

並列化によってプログラムの実行時間を短縮することができますが

完全な並列化をおこなってもオーバーヘッドのため14にはならない

時間

逐次 4並列並列化

14

オーバーヘッドが無視できる程度の大きい処理を並列化すべき(多重ループならば外側が望ましい)

並列化にはオーバーヘッドがつきものです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

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)

Page 54: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

オーバーヘッド(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

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)

Page 55: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

ロードバランス55

均等な処理の割り振り(load balancing)にも注意する必要があります

時間

逐次 4並列並列化

14

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)

Page 56: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

ループ構文とスケジューリング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)

Page 57: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

サイズ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)

Page 58: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 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)

Page 59: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

スケジューリングとロードバランス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)

Page 60: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

スケジューリングとロードバランス(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)

Page 61: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

同期 並列処理の制御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)

Page 62: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

暗黙の同期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)

Page 63: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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)

Page 64: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

バリア同期(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)

Page 65: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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)

Page 66: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

実行時ライブラリルーチンと環境変数

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)

Page 67: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

実行時ライブラリルーチン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)

Page 68: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

代表的なライブラリルーチン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)

Page 69: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

環境変数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)

Page 70: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

まとめ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)

Page 71: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

参考文献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)

Page 72: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

補足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)

Page 73: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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)

Page 74: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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)

Page 75: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

同期に関する各種構文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)

Page 76: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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)

Page 77: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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)

Page 78: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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)

Page 79: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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)

Page 80: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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)

Page 81: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

スレッドセーフ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)

Page 82: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

並列化率とアムダールの法則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)

Page 83: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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)

Page 84: 並列プログラミング 入門(OpenMP...2020/02/03  · 並列プログラミング 入門(OpenMP) 高度情報科学技術研究機構 1 登録施設利用促進機関

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)