• 検索結果がありません。

並列プログラミング入門(OpenMP編)

N/A
N/A
Protected

Academic year: 2021

シェア "並列プログラミング入門(OpenMP編)"

Copied!
101
0
0

読み込み中.... (全文を見る)

全文

(1)

並列プログラミング入門

(OpenMP編)

高度情報科学技術研究機構(RIST)

山本 秀喜

1 登録施設利用促進機関 /文科省委託事業「HPCIの運営」代表機関 一般財団法人 高度情報科学技術研究機構(RIST)

2019年1月17日

(2)

RIST主催の講習会等

2

HPCプログラミングセミナー

一般・初心者向け: チューニング・並列化

(

OpenMP

MPI)

「京」初中級者向け講習会

「京」利用者・利用予定者向け: 「京」に特化した内容

ワークショップ等

一般・経験者向け: ユーザー間の情報共有

RIST主催・共催の講習会・セミナー・ワークショップ一覧

https://www.hpci-office.jp/pages/seminar

RIST 講習会 で出てきます。多分。

(3)

Outline

3

はじめに(現在の

HPCについて)

並列処理

OpenMP入門

並列実行領域中のデータの属性

アクセス競合に注意すべきループ〜データ共有属性ミスの例

ループの並列化と依存性

Reduction演算

DOループのスケジューリング

並列の制御・同時処理

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

OpenMP並列化例

(4)

はじめに

現在の

HPC用コンピュータの形態

複数のコンピュータ群からなる

1台1台をノードと呼ぶ

性能を活かすにはノード

の並列化が必要

参考:「京」は約

8万ノード

マルチコア

CPU(メニーコア)

CPU内に複数のプロセッサーコア

パソコンでも主流

性能を活かすにはノード

の並列化が必要

参考:「京」は8コア

CPUが1個/ノード

アクセラレータ(省略)

Graphics Processing Unit(GPU)

本日の題目

ノード

並列化に多く用いられる

OpenMPの入門的内容

(ノード間並列化に使われる

MPIはこの後)

ネットワーク (インターコネクト) 4 ノード CPU メモリ ノード CPU メモリ ノード CPU メモリ ノード CPU メモリ Core Core Core Core Core Core Core Core Core Core Core Core Core Core Core Core

(5)

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

並列処理

(6)

並列処理とは

6

処理を分割して同時並列に実行すること

処理終了までの時間の短縮が目的

マルチコア環境では各コアに処理を分配したい。

逐次

時間

処理

処理

処理

処理

4並列

処理

処理

処理

処理

(7)

プロセスとスレッド

7

スレッド

プロセスより小さい実行単位(処理の分割単位)

1プロセス内のスレッドはメモリ空間を共有する。

1つのスレッドは1つのコアで実行される。

逐次実行中(通常)のプロセス

(シングルスレッド)

メモリ空間

スレッド

プロセス

メモリ空間

並列実行中のプロセス

(2スレッド)

プロセス

スレッド

スレッド

(8)

プロセスとスレッド

(cont.)

8

スレッド

プロセスより小さい実行単位(処理の分割単位)

マルチスレッドのプロセスはマルチコアの性能を引き出せる。

1スレッドプロセスを

処理中の2コア

CPU

2スレッドプロセスを

処理中の2コア

CPU

稼働中 空き

スレッド

稼働中 稼働中

スレッド

スレッド

CPU

CPU

コア

コア

コア

コア

(9)

おもな並列化方式

9

プロセス並列

スレッド並列

メモリ 空間 メモリ 空間

プロセス

プロセス

プロセス間 通信 メモリ空間

スレッド

スレッド

ノード

の並列

(分散メモリ並列、

ノード内の並列も可)

ノード

の並列

(共有メモリ並列)

MPI

(Message Passing Interface)

OpenMP

自動並列

プロセス

(10)

おもな並列化方式

(cont.)

10

ハイブリッド並列(

OpenMP+MPI)

プロセス間 通信 メモリ空間

スレッド

スレッド

プロセス

例えば、「京」コンピュータでは、通信量の削減の観点から、

ノード

OpenMP並列

または自動並列、ノード

MPI並列、

という両者を組み合わせたハイブリッド並列が推奨されている。

メモリ空間

スレッド

スレッド

プロセス

ノード

ノード

(11)

OpenMPのHello worldプログラムと

DOループの並列化を紹介します。

OpenMP入門

(12)

OpenMPによる並列化

12

OpenMP構文による並列化

do i = 1, 4000

A(i) = B(i) + C(i)

end do

!$omp parallel do

do i = 1, 4000

A(i) = B(i) + C(i)

end do

!$omp end parallel do

(13)

代表的な

OpenMP構文(Fortran)

13

代表的なOpenMP構文(Fortran)

!$omp parallel / !$omp end parallel

!$omp do

!$omp parallel do

!$omp parallel do reduction(+: …… )

!$omp critical

!$omp barrier

!$omp single

!$omp ordered

(14)

代表的な

OpenMP構文(C/C++)

14

代表的なOpenMP構文(C/C++)

#pragma omp parallel

#pragma omp for

#pragma omp parallel for

#pragma omp parallel for reduction(+: …… )

#pragma omp critical

#pragma omp barrier

#pragma omp single

#pragma omp ordered

#pragma omp parallel for

for ( i=0 ; i<4000 ; i++ ) {

A[i] = B[i] + C[i];

}

(15)

並列実行領域(

parallel構文)

15

program hello

write(*,*) ‘Hello world’

!$omp parallel

write(*,*) ‘Hello OpenMP world’

!$omp end parallel

end

●parallel構文

!$omp parallel [指示節 [,指示節]]

parallel~end parallelで囲まれた領域を並列実行します。

並列実行領域

(parallel region)

(16)

実行例:

Hello OpenMP world

16

$

並列実行領域の出力

(4並列実行)

(*) intel fortran: ifort

–openmp

hello.f

GNU: gfortran

–fopenmp

hello.f

「京」

login-node: frtpx

–Kopenmp

hello.f

(**) cshの場合: setenv OMP_NUM_THREADS 4

Hello world

Hello OpenMP world

Hello OpenMP world

Hello OpenMP world

Hello OpenMP world

$

OpenMPオプションをつけてコンパイル

frt -Kopenmp

hello.f

(*)

← スレッド数(並列数)を

環境変数で設定

$ export OMP_NUM_THREADS=4

(**)

← 実行

$ ./a.out

← 逐次部分からの出力

コンパイラによって

オプションが違う

(17)

OpenMP スレッドの動作

17

!$omp parallel

並列実行領域 逐次実行領域

!$omp end parallel

逐次実行領域 プログラム開始

マスター

スレッド

分岐(

fork): スレッドチーム結成(ワーカースレッド生成)

スレッド0

=マスタースレッド

スレッド2

スレッド3

スレッド1

合流(

join): スレッドチーム消滅(ワーカースレッド消滅)

プログラム終了

マスター

スレッド

(18)

パラレル構文の効果

18

Parallel構文

スレッドの分岐・合流を制御

処理の割り振りはしない。

並列化には

処理の割り振り、が必要

後述のワークシェアリング構文

を利用する。

逐次

処理

処理

処理

処理

処理

処理

処理

処理

時間

並列?

(19)

ワークシェアリング構文

19

Parallel構文は、スレッドチームの分岐/合流を制御する。

並列化には、さらに、処理の割り振り(ワークシェアリング)が必要

OpenMPではワークシェアリング構文を用いる。

ワークシェアリング構文の種類

ループ構文

doループを分割実行

single構文

生成されたスレッドのうち1つのスレッドのみが実行

sections構文

依存関係のない異なる処理をそれぞれのスレッドで実行

workshare構文(Fortranのみ)

fortran90以降の配列代入文などを分割実行

本資料では、ループ構文を主に扱います。

(20)

DOループのワークシェアリング

20

●do構文(ループ構文)

・並列実行領域において、doループを分

割し、チーム内の各スレッドに割り当

てます。

・デフォルトでは、均等に分割され、各ス

レッドにより実行されます。

do i = 1, 4000

V(i) = X(i) + Y(i) end do

ループ長

n=4000

n=4000 1 2 3

逐次

1000 2000 3000 4000

スレッド0が実行

スレッド1が実行

スレッド2が実行

スレッド3が実行

4並列

4並列で実行

!$omp parallel !$omp do !$omp end do !$omp end parallel

(21)

DOループのワークシェアリングの

書式(

Fortran)

21

do i = 1, 4000

V(i) = X(i) + Y(i)

end do

この

DOループを並列化する

●パラレルループ構文(パラレル構文とループ構文の複合)

!$omp parallel do [指示節 [,指示節]]

doループ

[!$omp end parallel 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

●パラレル構文

!$omp parallel [指示節 [,指示節]]

並列実行領域

!$omp end parallel (省略不可)

parallel~end parallelで囲まれた領域を並列実行 ●ループ構文(DO構文)

!$omp do [指示節 [,指示節]]

doループ

(22)

DOループのワークシェアリングの

書式(

Fortran)の省略形

22

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

!$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 parallel

(23)

Forループのワークシェアリングの

書式(

C言語)

23

for ( i=0 ; i<4000 ; i++ ) { V[i] = X[i] + Y[i];

}

この

forループを並列化する

●パラレルループ構文(C言語)

#pragma omp parallel for [指示節 [,指示節]]

後続のforループを各スレッドで分割して並列実行します。

#pragma omp parallel for for ( i=0 ; i<4000 ; i++ ) {

V[i] = X[i] + Y[i]; }

#pragma omp parallel {

#pragma omp for

for ( i=0 ; i<4000 ; i++ ) { V[i] = X[i] + Y[i]; }

}

●ループ構文(for構文)(C言語)

#pragma omp for [指示節 [,指示節]]

後続のforループを分割して各スレッドに割り当てます。

●パラレル構文(C言語)

#pragma omp parallel [指示節 [,指示節]]

(24)

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

shared属性とprivate属性)

並列実行領域中のデータの属性

(25)

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

Shared

データ: 全てのスレッドからアクセス可能なデータ

Private

データ: 各スレッド固有の、他のスレッドからは見えないデータ

OpenMPでは、データ共有属性をプログラマの責任で設定する必要があります。

 誤った設定は、不正な結果(バグ)の原因となります。

並列実行領域中のデータの属性

25 !$omp parallel do do i = 1, 4000

V(i) = X(i) + Y(i) end do

private

データ

private

データ

shared

データ

V(:), X(:), Y(:)

i

i

スレッド

スレッド

(26)

データ共有属性の宣言

parallel構文やdo構文の指示節として指定します。

暗黙のデータ共有属性

並列実行領域において、指定の無いほとんどのデータは、デフォルト

shared属性(詳細は後述)

parallel doまたはdo構文のループ内のループインデックス変数は、そ

の構文内で

private属性

暗黙のデータ共有属性により、上記の例では、

private(i) shared(V, X, Y)」を省略可

データ共有属性の宣言

26

!$omp parallel do

private(i)

shared(V, X, Y)

do

i

= 1, 4000

V(

i

)

=

X(

i

)

+

Y(

i

)

end do

(27)

Shared属性

27 プログラム開始 マスタースレッド0 マスタースレッド0 スレッド1 スレッド2 スレッド3

V

X

Y

shared(V, X, Y)

並列実行領域 

Shared

データ

すべてのスレッドから参照可能。

並列実行領域開始前と同一の(メモリ領域に記憶される)変数

shared指示節で指定されたデータ、 あるいは暗黙のshared属性データ

(28)

Private属性

28 プログラム開始 マスタースレッド0 マスタースレッド0 スレッド1 スレッド2 スレッド3

V

X

Y

shared(V, X, Y)

i

private( i )

i

0

i

1

i

2

i

3

互いにアクセスすることはできない

Private

データ

各スレッドに固有のデータ。他のスレッドからはアクセス不可。

並列実行領域前の対応する変数とは別の実体(メモリー領域)を持つ。

初期値は未定義。

Private指示節で 指定されたデータ

※ 例えば

i

0

はスレッド0に固有の

i を表す。

(29)

暗黙のデータ共有属性(

Fortran)

29 ・・・ !$omp parallel !$omp do do i=1,4 X(i)=i call sub(X(i)) end do !$omp end do

!$omp end parallel

・・・ subroutine sub(y) common /com/ n save ymax real :: a = 1.0 real :: b ・・・ end

並列実行領域内

 Private: ループ構文内のループインデックス ( i ) 他にParallel構文内の逐次DOループインデックス、 DO型反復のインデックスもprivate  Shared: 何の指定もない変数 ( X(:) )  default指示節により変更可(暗黙のデータ共有属性)

default(shared), default(private), default(none)

none」の場合は明示的に指定しなければならない

コールされたルーチン内

 仮引数はcall元のルーチンに従う

yのアドレスはcall元のX(i)を指しているので、その設定に従う。

 Shared: COMMON/SAVE文の変数 (n, ymax, a)

他にmoduleやsave属性の変数もshared

 Private: 上記以外の変数 (b)

※詳しくは仕様書を参照してください。

a = 1.0 のように初期値

(30)

暗黙のデータ共有属性(

Fortran)

並列実行領域内

30

・・・

!$omp parallel

!$omp do

do

i

=1,4

X(

i

)

=

i

call sub(

X(

i

)

)

end do

!$omp end do

!$omp end parallel

・・・

ループ構文内のループインデックス

(

i

)は

PRIVATE

他に、 parallel構文内の逐次のdoループインデックスや、do型反復の インデックスも、PRIVATE

上記以外の何の指定もない変数

(

X(

:

)

)は、

SHARED

この「暗黙のデータ共有属性」は、default指示節 により変更可能。

default(shared), default(private), default(none)

(31)

暗黙のデータ共有属性(

Fortran)

コールされたルーチン内

31

subroutine sub(

y

)

common /com/

n

save

ymax

real ::

a

= 1.0

real ::

b

・・・

end

仮引数は

call元のルーチンに従う。

yのアドレスはcall元のX(i)を指しているので、そ

の設定に従う。

common/save属性の変数 (

n

,

ymax

,

a

)は

SHARED

。他に

module変数も

SHARED

初期値指定のある変数も

save属性なので要注意

上記以外

(

b

)は、

PRIVATE

※厳密な規則は仕様書を参照してください。

※この例は、スレッドセーフではありません。

(32)

データ共有属性の設定ミス → アクセス競合

アクセス競合に注意すべきループ

〜データ共有属性ミスの例〜

(33)

一時変数を含むループ

33

●private属性の指定忘れに注意

!$omp parallel do do i = 1, 4 t = X(i) + Y(i) V(i) = V(i) + t * t end do スレッド0 同時 更新

・t をprivate属性指定しないと、t は

shared属性

なので、 t はそれぞれのスレッド

から同時更新され、タイミングによって結果が異なってしまいます。

!$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

(34)

一時変数を含むループ(cont.)

34

●private属性指定の見落とし

-右の例ではループインデックス i 以外、

すべてshared属性となってしまう。

shared(t, V, X, Y)

V

X

Y

t

t

!$omp parallel do

do i = 1, 4000

t

= X(i) + Y(i)

V(i) = V(i) +

t * t

end do

マスタースレッド0 スレッド0が更新

t

=…

t * t

スレッド0が結果を読込 スレッド1が更新 スレッド1が結果を読込

プログラマが期待?した動作

各スレッドの更新→読込に、運良く

重なりが無ければ正しい結果となる。

(35)

一時変数を含むループ(cont.)

35

●private属性指定の見落とし

-右の例ではループインデックス i 以外、

すべてshared属性となってしまう。

shared(t, V, X, Y)

!$omp parallel do

do i = 1, 4000

t

= X(i) + Y(i)

V(i) = V(i) +

t * t

end do

マスタースレッド0 スレッド0が更新 別スレッドの 意図せぬ更新 スレッド0が 不正な結果 を読込

異常終了せず、常に不正な値を

与えるわけではないので、

表面化しにくい。

意図しないタイミングでのtの更新が発生

する可能性があり、

時々

不正な結果とな

る。

t

=…

t * t

V

X

Y

t

t

(36)

一時変数を含むループ(不正の回避)

36 プログラム開始 マスタースレッド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

t

0

スレッド1

t

1

スレッド2

t

2

スレッド3

t

3

プログラムは正常に動作します。

(37)

サブルーチンコールのあるループ

37 …… !$omp parallel do do i = 1, 4 call SUB( 〜 ) end do …… スレッド0 スレッド1 Subroutine SUB( 〜 ) …… call SUB2( 〜 ) …… end Subroutine SUB2( 〜 )

COMMON /COM/ WORK(100) WORK(1) = 〜 …… 〜 = WORK(1) …… end

WORK(1) = 〜

……

〜 = WORK(1)

WORK(1) = 〜

……

〜 = WORK(1)

同時更新の 恐れあり

WORK(1)

サブルーチン内部のcommonやsave属性の変数に注意してください。

上記の例では、他のサブルーチンで/COM/を利用している場合は、threadprivate構文で

private化します。(詳細はおまけの「共有変数がある場合の注意」を参照)

サブルーチン内でも commonの変数はshared

(38)

スレッドセーフとは(競合と排他制御)

38

スレッドセーフなルーチン

複数のスレッドで同時に実行しても意図した機能を果たすルーチン

 例えば、間接参照を含むルーチンで同一のアドレスをアクセスしてしまうような処理は、競合 が発生しており、スレッドセーフではなくなります。 

スレッドセーフでない処理の例:

 COMMONやSAVE属性の変数にアクセスしている関数・サブルーチンで、排他制御が正しく できていないルーチン  ローカル変数であっても、コンパイル時に-saveオプションを付加した場合  同一のユニット番号に対するREADやWRITEなどのI/O処理(Fortranの規格ではスレッドセー フを保証していません。)  本資料では理解しやすさのため、並列実行領域内でwrite文を実行するプログラム例が多数ありま すが、本来はcritical構文等で排他制御をすべきです。

スレッドセーフでない処理を並列実行してしまうと、

 計算結果が不正だったり、プログラムが異常終了する場合があります。  常に発生するとは限らない上、実行環境により頻度が変わるため、問題が発見しにくい場 合があります。

(39)

PRIVATE/SHARED属性宣言のミス(アクセス競合)

不正な結果(バグ)の原因

実行の度に結果が異なる場合がある。

発生頻度は、2回に1度のこともあれば、

数千回に1回のことも → 発見しにくい

OpenMPではデータ共有属性を注意して

設定する必要がある。

コンパイラによっては警告を出してくれることもある。

OpenMP化によるミスの特徴

39

(40)

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

ループの並列化と依存性

(41)

並列化できないループ・

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

41

ループ構文で並列化できないループ:

exit等、途中でループを抜ける命令があるループ。

goto~continueのループ構造、等

コンパイルできない。

並列化してはいけないループ(次スライドで説明):

他のサイクルの結果を参照するループ

=反復(サイクル)間に

依存性

のあるループ

!$omp parallel do do i = 1, N if ( 〜 ) exit A(i) = A(i) + 1.0 end do !$omp parallel do do i= 2, N V(i) = V(i - 1) + a end do

OpenMPで“並列”実行できてしまうが、

逐次実行と異なる結果を与えてしまう。

(42)

並列化してはいけない例

(1)フロー依存のあるループ(逐次)

42

do i = 2, 7

V(

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)

を用いて計算する。

分割

(43)

並列化してはいけない例

(1)フロー依存のあるループ

43 !$omp parallel do do 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)の値が必要

!$omp parallel do private(t) do i = 2, 7 t = V( i – 1 ) V( i ) = t + a end do 一時変数を使っ ても同じ

(44)

並列化してはいけない例

(2)逆依存のあるループ(逐次)

44

do i = 1, 6

V(

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)

上書きされます。

分割

(45)

並列化してはいけない例

(2)逆依存のあるループ

45 !$omp parallel do do 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)の値を上書き スレッド1

!$omp parallel do private(t) do i = 1, 6 t = V( i +1 ) V( i ) = t + a end do 一時変数を使っ ても同じ

(46)

並列化してはいけない例

3)重なりのある間接参照のあるループ

46

●間接参照のあるループ

!$omp parallel do

do i = 1, 4

V( List( i ) ) = X(i) + Y(i)

end do

スレッド0

・例えば、List(2) = List(4) の場合、どちらの間接参照が後にアクセスされる

かによって結果が変化します。(順番に依存)

⇒ 配列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)

(47)

総和などの計算をおこなう

reduction指示節を

説明します。

Reduction演算

(48)

総和計算の並列化

48

●総和の計算を並列化する

!$omp parallel do do i = 1, 4 S = S + V(i) end do スレッド0 スレッド1 同時更新の 可能性

・Sがshared属性ならば、スレッド0とスレッド1がそれぞれ勝手なタイミングで

Sの値を更新するため、結果は不定となります。

・もしSをprivate属性にすると、全体の総和を得ることはできません。

S

解決するには?

S

=

S

+ V(1)

S

=

S

+ V(2)

S

=

S

+ V(3)

S

=

S

+ V(4)

(49)

総和計算の並列化

(cont.)

49

●総和の計算を並列化する ⇒

reduction指示節

!$omp parallel do

reduction (

+

: S)

do i = 1, 4 S = S + V(i) end do スレッド0 スレッド1

S

0

= 0

S

0

=

S

0

+ V(1)

S

0

=

S

0

+ V(2)

S

1

= 0

S

1

=

S

1

+ V(3)

S

1

=

S

1

+ V(4)

(注)

Reduction演算は、計算の順序が逐次演算と異なります。

そのため、丸め誤差により結果が微妙に異なる可能性があります。

数値計算的には V(1) + V(2) + V(3) +V(4) ≠ { V(1) + V(2) } + { V(3) +V(4) }

Sは

特殊な

private変数として扱

われる。(reduction変数)

初期値

加算順序はスレッド番号順 とは限りません。

S = S

+

S

0

+

S

1

元の変数に加算

(50)

Reduction指示節

50

Reduction演算

 複数の変数を何らかの演算で一個の変数に縮約する操作

 一般例: r = r op expr の繰り返し等(r: reduction変数, expr: rを参照しない式)

Reduction指示節により・・・

ループ内でreduction変数のprivateなコピーを作成し、

ループ終了後、各スレッドの演算結果を元の変数に縮約する。

!$omp parallel do

reduction(op : r

1

[ , r

2

] )

op

: reduction演算子

(+ , * , - , .and. , .or. , .eqv. , .neqv. , max, min, iand, ior, ieor)

r

1

[ , r

2

]

: reduction変数(複数指定可)

(51)

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

(52)

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

OpenMPで用意されているスケジューリングの方法

について説明します。

DOループのスケジューリング

(53)

オーバーヘッド

53

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

完全な並列化をおこなっても

オーバーヘッドのため、1/4にはならない。

時間

逐次

4並列

並列化

1/4

オーバーヘッドが無視できる程度の

大きい処理を並列化すべき。

(多重ループならば外側が望ましい。)

並列化にはオーバーヘッドがつきものです。

• スレッド生成・同期

• 並列化に伴うコード変更による処理の増加等

オ ー バ ー ヘ ッ ド オ ー バ ー ヘ ッ ド

(54)

オーバーヘッド

(cont.)

54 !$omp parallel do do i = 1, 10 〜〜〜 end do

!$omp end parallel do

!$omp parallel do

do i = 1, 1000000 〜〜〜

end do

!$omp end parallel do

反復数の少ないループより多いループの方が

オーバーヘッドが相対的に小さくなります。

!$omp parallel do do j = 1, n do i = 1, n 〜〜〜 end do end 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)

ロードバランス

55

均等な処理の割り振り

(load balancing)にも注意する必要があります。

時間

逐次

並列化

4並列

1/4

idle

idle

idle

不均等(インバランス)な割振りで

は、期待した性能が出ないことが

あります。

(56)

ループ構文とスケジューリング

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=“guided, 1”

(57)

サイズ2のチャンクに分割し、順繰りに配分

STATIC(静的) スケジューリング

57

Schedule指示節により、ループ反復の割当方法を変更できます。

schedule(static)

schedule(static, 2)

逐次

静的割り当て:

実行前に割り当てを決める

全てをマスタースレッドが処理 均等に分割<デフォルト> サイズ1のチャンクに分割し、順繰りに配分

schedule(static, 1)

デフォルト

!$omp parallel do schedule(

スケジューリングの種別

)

(58)

schedule(dynamic, 2)

動的割り当て:

処理の終わったスレッドが次のチャンクに取りかかる。

サイズ2のチャンクに分割し、動的に割り当て (デフォルトのチャンクサイズは1)

DYNAMIC(動的) スケジューリング

58

Schedule指示節により、ループ反復の割当方法を変更できます。

!$omp parallel do schedule(

スケジューリングの種別

)

サイクルごとの処理量が不均一な時に効果的だが

staticスケジューリングよりオーバーヘッドが大きい

(59)

スケジューリングとロードバランス

59

●三角行列とロードインバランス

do j = 1, n do i = j, n

A(i, j) = A(i, j) + B(i, j) end do end do

j

i

!$omp parallel do do j = 1, n do i = j, n

A(i, j) = A(i, j) + B(i, j) end do end do 単純に 4並列実行

スレッド 0

3 が担当

※処理量に差 ⇒ ロードインバランス状態

(60)

スケジューリングとロードバランス

(cont.)

60

●ロードバランスの改善

!$omp parallel do schedule(dynamic)

do j = 1, n do i = j, n

A(i, j) = A(i, j) + B(i, j) end do

end do

スレッド 0 1 2 3 0 1 2 3 0 1 2 3が担当

※ チャンクを細かくするとデフォルトのstaticよりロードバランスが改善し、負荷が均等になります。 Dynamicは完璧なように思えますが、オーバーヘッドが大きいので注意が必要です。

!$omp parallel do schedule(static, 1)

do j = 1, n do i = j, n

A(i, j) = A(i, j) + B(i, j) end do

end do

(61)

並列処理領域内の逐次処理

同期処理

排他制御

並列の制御・同期処理

61

排他制御

同期処理

(62)

暗黙の同期

62

●ループ構文などのワークシェアリング構文の出口では、

暗黙に

同期処理が行われます

!$omp parallel !$omp do do i = 1, n

V(i) = V(i) + X(i) end do

!$omp end do !$omp do

do i = 1, n

W(i) = W(i) + Y(i) end do

!$omp end do

!$omp end parallel

※各DOループの終了時に、全スレッドの処理終了を待ってから、 次のDOループの処理に移ります。 待ち合わせのための若干のオーバーヘッドがかかります。 1 2 3 マスタースレッド0 暗黙の同期 暗黙の同期

待ち合わせ

待ち合わせ

(63)

暗黙の同期の回避(

nowait指示節)

63

●暗黙の同期処理が不要ならば、

nowait

指示節により、同期を回

避できます。

!$omp parallel !$omp do do i = 1, n

V(i) = V(i) + X(i) end do

!$omp end do nowait

!$omp do

do i = 1, n

W(i) = W(i) + Y(i) end do

!$omp end do

!$omp end parallel

1 2 3 マスタースレッド0 暗黙の同期

同期を回避

※ nowait指示節を指定すると、doループ終了時の待ち合わせをせず、 直ちに次の処理に移ります。これにより待ち合わせのオーバーヘッドを 減らすことができますが、誤った箇所にnowaitを指定すると不正な結果が 得られることがあります。

待ち合わせ

(64)

ループ内の暗黙の同期

64

二重ループの内側

を並列化する場合の

暗黙の同期

!$omp parallel do j = 2, m !$omp do do i = 1, n

W(i, j) = W(i, j-1) + Y(i, j) end do

!$omp end do

end do

!$omp end parallel

j = 2

j = 3

j = 4

1 2 3 マスタースレッド0

※ j ループが反復するごとに同期が発生します。

待ち合わせ

待ち合わせ

(65)

ループ内の暗黙の同期の回避

65

●二重ループの内側を並列化する場合の

暗黙の同期の回避

!$omp parallel do j = 2, m !$omp do do i = 1, n

W(i, j) = X(i, j-1) + Y(i, j) end do

!$omp end do

nowait

end do

!$omp end parallel

待ち合わせせず、 次の j へ進む。

j = 2

j = 3

j = 4

1 2 3 マスタースレッド0

※ j ループの前の反復の計算を全スレッドが完了するのを待つ必要が無い場合のみ、

nowait

を使用できます。

ループの繰り返し依存がないか注意が必要です。

j ループの反復数分の同期オーバーヘッドの節約になります。

(66)

並列実行領域中の逐次処理

66

並列実行領域の中に並列化できない逐次処理を含めたい場合、並列

実行領域を終わらせることなく、1つのスレッドによる逐次処理を行う領

域を設けることができます。

構文

内容

single

指定された領域の処理を、一つのスレッドが実行します。

(マ

スタースレッドであるとは限りません)

※指定領域の出口で、暗黙の同期を行います。

master

指定された領域の処理を、マスタースレッドが実行します。※指

定領域の出口で、暗黙の同期は行いません。

…… !$omp parallel …… !$omp single

write(6, * ) ‘Serial processing’

!$omp end single

……

!$omp end parallel

シングル処理

(67)

バリア同期(

barrier構文)

67 !$omp parallel …… !$omp master allocate( V(n) ) !$omp end master

!$omp barrier !$omp do do i = 1, n V(i) = Y(i) end do !$omp end do

!$omp end parallel

※master指示節は、暗黙の同期(待ち合わせ)を行いませんので、 スレッド1〜3の後の処理(緑)を待たせるためには、barrier指示節が必要です。 待ち合わせ

●barrier構文

- スレッドの待ち合わせ(同期)を行います。

暗黙の同期 1 2 3 マスタースレッド0 配列Vの準備

待ち合わせ

待ち合わせ

(68)

critical構文(排他制御)

68

real :: sum, xcount, function real :: x(n, m)

……… do j = 1, m

!$omp parallel shared ( x, xcount ) !$omp single

xcount = 0.0 !$omp end single !$omp do

do i = 1, n

!$omp critical

if ( x( i, j ) .lt. 1.0 ) then

xcount = function ( xcount, x( i, j ) ) end if

!$omp end critical

end do

!$omp do reduction ( +: sum ) do i = 1, 4000

sum = sum + x( i, j ) + xcount end do

!$omp end parallel end do end スレッド0 1 2 3

一人ずつアクセスする。

critical構文は、指定範囲について複数スレッドの処理が重ならないよう

にし(排他制御)、アクセス競合を回避する。

(69)

その他の同期・排他処理構文

69

構文

内容

atomic

critical指示節と同様に排他処理をしますが、こちらの方が高速

です。ただし、インクリメントなどの特定の演算の文にのみ使用

できます。

flush

フラッシュ操作を行います。あるスレッドが持つ一時的なビュー

(レジスタやキャッシュ等の内容)をメモリの内容と一致させます。

ordered

指定したループ領域において、逐次実行した場合と同じ順序で

実行するよう順序付けを行います。

(70)

実行時ライブラリルーチンと

環境変数

(71)

実行時ライブラリルーチン

71

OpenMPでは種々のルーチンが用意されている。

OpenMP API実行時ライブラリルーチン

並列実行環境の制御や問合せを行う実行環境ルーチン

データへのアクセスを同期して行うためのロックルーチン

時間計測ルーチン

利用するにはヘッダファイルを読み込む

include ‘omp_lib.h’

⇔ 一般的な

Fortran

use omp_lib

Fortran90モジュールファイル

(72)

代表的なライブラリルーチン

72

ルーチン名

返値

内容

omp_get_thread_num

integer

このルーチンを呼び出したチームに属する

スレッド番号

を返します。

0〜[スレッド数-1]の値を返します。マスタースレッドは0

omp_get_max_threads

integer

並列実行領域で利用できる

スレッド数の最大値

を返

します。

(並列実行領域に入る前でも利用できます。)

omp_get_num_threads

integer

現在の並列実行領域を

実行中

のスレッド数を返しま

す。

omp_in_parallel

logical

活動状態の並列実行領域内から呼び出された場合

.true.」、それ以外は「.false.」を返します。

並列区間・非並列区間の両方から呼ばれるサブルーチンの分岐に利用できま す。

(73)

条件付きコンパイル

73 integer :: thrdnum, me thrdnum = 1 me = 0 !$ thrdnum = omp_get_max_threads() !$ me = omp_get_thread_num() 

!$ で始まる行は、OpenMPでコンパイルする時のみ有効となります。

OpenMPを使わない場合のエラーを防ぐことができます。

integer :: thrdnum, me thrdnum = omp_get_max_threads() me = omp_get_thread_num()

(プリプロセッサーの構文

”#ifdef _OPENMP”を用いる方法もあります。)

以下のプログラムのomp_get_max_threads等はOpenMPのライブラリ関数 なので、OpenMP並列を指定しない場合、リンク時にエラーになります。 以下のように!$”を用いて書き換えると、通常の(OpenMPを使用しない) コンパイルの場合、コメント行と見なされ、互換性を保つことができます。

(74)

環境変数

74

環境変数

内容

OMP_NUM_THREADS 並列実行領域で使用するスレッドの数を設定します。 OMP_SCHEDULE スケジュールタイプがruntimeであるループ指示文のスケジュー リングを制御します。(デフォルトはstatic) OMP_STACKSIZE 各スレッドが実行時に利用するスタックサイズを指定します。ス レッドごとの固有データなどのメモリ領域に利用されます。

(注)OMP_STACKSIZE

大きなprivate属性の配列を用いるプログラムでは、スレッドのprivate用のスタックサ

イズが不足する場合があります。そのような場合は、この環境変数を十分大きい値

で設定します。

OpenMPプログラムの実行に影響する主な環境変数

(75)

OpenMP並列化例

OpenMPに限らず並列化の一般知識の補足

OpenMPに関する情報源

補足

(76)

OpenMPによる並列化の簡単な例

OpenMP並列化例

76

※ ここでは、OpenMP並列化の説明を行います。 逐次での高速化は「チューニング技法」の資料を 参考にしてください。

(77)

OpenMP並列化例:

個別要素法/分子動力学法の例

77

各粒子にかかる力の計算

入力データ

粒子数

n

、粒子の座標

x(n)

簡単のため

1次元とする。

粒子

i,j

間の相互作用

f

ij

簡単のため、距離に反比例

f

ij

= 1/(x(j) - x(i))

各粒子にかかる力の計算

粒子

1にかかる力

f(1)の場合

f(1)=

f

12

+f

13

+f

14

+f

15

+f

16

これを

f(1)〜f(6)の

全ての粒子について計算

図 粒子1にかかる力(赤矢印)

1

2

3

5

6

4

1

f

12

f

16

f

13

f

15

f

14

n = 6

(78)

粒子

iにかかる力の合計をf(i)に保存

f(1) =

f

12

+f

13

+f

14

+f

15

+f

16

f(2) =

-f

12

+f

23

+f

24

+f

25

+f

26

f(3) =

-f

13

-f

23

+f

34

+f

35

+f

36

f(4) =

-f

14

-f

24

-f

34

+f

45

+f

46

f(5) =

-f

15

-f

25

-f

35

-f

45

+f

56

f(6) =

-f

16

-f

26

-f

36

-f

46

-f

56

作用反作用の法則

(

f

ji

= –f

ij

)より、

対称な要素は逆符号で同じ値。

OpenMP並列化例:

個別要素法/分子動力学法の例

78

図 作用反作用の法則

(

f

ji

= –f

ij

)

1

2

3

5

6

4

1

f

12

f

16

f

13

f

15

f

14

-f

15

-f

16

-f

13

-f

14

f

21

= -f

12

n = 6

(79)

OpenMP並列化例:

逐次プログラム

79

dimension x(n), f(n)

do i=1,n-1

do j=i+1,n

fij = 1.d0/(x(j)-x(i))

f(i) = f(i) + fij

f(j) = f(j) - fij

end do

end do

n: 粒子数

x(n): 粒子のx座標の配列

f(n): 粒子にかかる力の配列

一つfijを計算したら、 作用・反作用がかかる 配列fの2粒子の要素に 加算。 粒子i,jのループ(i<j)

f(1)=

f

12

+f

13

+f

14

+f

15

+f

16

f(2)=

-f

12

+f

23

+f

24

+f

25

+f

26

f(3)=

-f

13

-f

23

+f

34

+f

35

+f

36

f(4)=

-f

14

-f

24

-f

34

+f

45

+f

46

f(5)=

-f

15

-f

25

-f

35

-f

45

+f

56

f(6)=

-f

16

-f

26

-f

36

-f

46

-f

56

i=1

i=2

i=3

i=4

i=5

図 粒子数

n = 6 の場合の計算内容

f(1)=

f

12

+f

13

+f

14

+f

15

+f

16

f(2)=

-f

12

f(3)=

-f

13

f(4)=

-f

14

f(5)=

-f

15

f(6)=

-f

16

i=1

i=1の演算実行後の

配列

f の状態

最終結果

fij: 粒子iへのjによる力 の計算

n = 6

(80)

OpenMP並列化例:

OpenMP並列化時の検討項目

80

並列化時の検討項目

(1)並列化するループの選択

(2)並列化可能性(計算の順番の依存性)の検討

(3)変数のデータ共有属性の設定

(4)スケジューリングの選択

do i=1,n-1

do j=i+1,n

fij = 1.d0/(x(j)-x(i))

f(i) = f(i) + fij

f(j) = f(j) - fij

end do

(81)

OpenMP並列化例:

(1)並列化するループの選択

81

オーバーヘッドの観点から、

外側のループ

の並列化が望ましい。

do i=1,n-1

do j=i+1,n

fij = 1.d0/(x(j)-x(i))

f(i) = f(i) + fij

f(j) = f(j) - fij

end do

end do

結論:外側の

ループを並列化したい。

i に関する

(82)

OpenMP並列化例:

(2)並列化可能性の検討

82

並列化可能性(計算の順番の依存性)の検討

i の加算・減算の順番を変えても配列 f は不変

→ 依存性無し (丸め誤差は存在する。)

f(1)=

f

12

+f

13

+f

14

+f

15

+f

16

f(2)=

-f

12

+f

23

+f

24

+f

25

+f

26

f(3)=

-f

13

-f

23

+f

34

+f

35

+f

36

f(4)=

-f

14

-f

24

-f

34

+f

45

+f

46

f(5)=

-f

15

-f

25

-f

35

-f

45

+f

56

f(6)=

-f

16

-f

26

-f

36

-f

46

-f

56

i=1

i=2

i=3

i=4

i=5

どの

iを先に計算

しても最終的な

配列

f(i)の値は同じ。

(83)

OpenMP並列化例:

(3)変数のデータ共有属性の設定

83

スレッド間のアクセス競合が発生する変数を洗い出す。

f(1)= f12 +f13 +f14 +f15 +f16 f(2)= -f12 +f23 +f24 +f25 +f26 f(3)= -f13 -f23 +f34 +f35 +f36 f(4)= -f14 -f24 -f34 f(4)= +f45 +f46 f(5)= -f15 -f25 -f35 f(5)= -f45 +f56 f(6)= -f16 -f26 -f36 f(6)= -f46 -f56

i=1 i=2 i=3 i=4 i=5

スレッド0の計算(

i=1〜3)

上の例では、スレッド間で要素f(4)〜f(6)の同時更新(競合)のおそれがあるので、 スレッドはそれぞれ独自の f を持つ必要がある。また、最後に各スレッドの f を要素ごとに 合計する必要があるので、配列freductionで指定する必要がある。

スレッド1の計算(

i=4〜5)

配列

fに関する演算の2スレッド実行の例

(84)

OpenMP並列化例:

(3)

変数のデータ共有属性の設定

cont.

84

スレッド間のアクセス競合が発生する変数を洗い出す。

特に並列ループ内の左辺にある変数に注意する。

!$OMP PARALLEL DO ...

do i=1,n-1

do

j

=i+1,n

fij = 1.d0/(x(j)-x(i))

f(i) = f(i) + fij

f(j) = f(j) - fij

end do

end do

fijの値はスレッドごとに異なる

→要private化

ループ変数

j

自動的に

private。

!$OMP PARALLEL DO

REDUCTION(+:f)

PRIVATE(fij)

結論:配列

fの

reduction

変数化

変数

fijの

private

化が必要

(85)

!$OMP PARALLEL DO REDUCTION(+:f) !$OMP& PRIVATE(fij)SCHEDULE(STATIC,1)

do i=1,n-1 ....

OpenMP並列化例:

(4)スケジューリングの選択

85

f

12

+f

13

+f

14

+f

15

+f

16

-f

12

+f

23

+f

24

+f

25

+f

26

-f

13

-f

23

+f

34

+f

35

+f

36

-f

14

-f

24

-f

34

+f

45

+f

46

-f

15

-f

25

-f

35

-f

45

+f

56

-f

16

-f

26

-f

36

-f

46

-f

56

f

12

+f

13

+f

14

+f

15

+f

16

-f

12

+f

23

+f

24

+f

25

+f

26

-f

13

-f

23

+f

34

+f

35

+f

36

-f

14

-f

24

-f

34

+f

45

+f

46

-f

15

-f

25

-f

35

-f

45

+f

56

-f

16

-f

26

-f

36

-f

46

-f

56

!$OMP PARALLEL DO REDUCTION(+:f) !$OMP& PRIVATE(fij) do i=1,n-1 ....

●デフォルトのスケジューリング

●チャンクサイズを小さく(1に)した例

インバランス大

インバランスが緩和

24: 6

18:12

加速率=30/24 = 1.25 加速率=30/18= 1.67

6粒子・2スレッドでのロードバランス

6粒子・2スレッドでのロードバランス

スレッド0

スレッド1

スレッド0

スレッド1

(86)

OpenMP並列化例(まとめ)

86

個別要素法/分子動力学法の例

並列化時の検討項目

(1)並列化するループの選択 外側が望ましい。 (2)並列化可能性 (計算の順番の依存性)の検討 並列ループの各サイクルの実行順が、 変わっても同じ結果か確認する。 (3)変数のデータ共有属性の設定 左辺の変数に注意し、必要に応じて、 PRIVATEやREDUCTIONの宣言をする。 (4)スケジューリングの選択 インバランスを緩和するような スケジューリングを選択する。 ※複数CPU/ノードの機種(cc-NUMA)の場合、 ファーストタッチの検討も必要(本講座では省略)

!$OMP PARALLEL DO

REDUCTION(+:f)

!$OMP&

PRIVATE(fij)

SCHEDULE(STATIC,1)

do i=1,n-1

!←並列化するループ

do j = i+1, n !

! ループ変数は自動的にprivateとなる。

fij = 1.d0/(x(j)-x(i))

! fijの値は i に依存→要private化

f(i) = f(i) + fij

f(j) = f(j) - fij

! ここで j ≠ i の要素f(j)を更新する。 ! 総和の計算 → 要reduction変数化

end do

end do

参照

関連したドキュメント

竣工予定 2020 年度 処理方法 焼却処理 炉型 キルンストーカ式 処理容量 95t/日(24 時間運転).

震災発生時のがれき処理に関

処理処分の流れ図(図 1-1 及び図 1-2)の各項目の処理量は、産業廃棄物・特別管理産業廃 棄物処理計画実施状況報告書(平成

過水タンク並びに Sr 処理水貯槽のうち Sr 処理水貯槽(K2 エリア)及び Sr 処理水貯槽(K1 南エリア)の放射能濃度は,水分析結果を基に線源条件を設定する。RO

(注)

処理水 バッファ タンク 原子炉へ RO処理水 貯槽 CST 原子炉へ PP淡水化装置 (建屋内RO)淡水化処理水

英国 Sellafield GeoMelt 燃料スラッジ,汚染土壌 模擬 実機. 英国 Sellafield 容器保管

ALPS 処理水希釈放出設備は通常運転~停止の他, 「意図しない形での ALPS