OpenMP 並列解説 1
人が共同作業を行うわけ 田植えの例 重いものを持ち上げる 田おこし 代かき 苗の準備 植付 共同作業する理由 1. 短時間で作業を行うため 2. 一人ではできない作業を行うため 3. 得意分野が異なる人が協力し合うため ポイント 1. 全員が最大限働く 2. タイミングよく 3. 作業順序に注意 4. オーバーヘッドをなくす 2
倍率 効率 並列化率と並列加速率 並列化効率の関係 並列加速率 並列化効率 2 1.8 1.6 1.4 1.2 1 0.8 1 5 9 13 プロセッサ数 1.2 1 0.8 0.6 0.4 0.2 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 プロセッサ数 並列化率 0.4 のプログラムを並列化した場合 並列化率 0.9 なら 3
並列計算機の種類 ( 共有メモリ :SMP 分散メモリ :DMP) C P U C P U 共有メモリ メモリ C P U C P U 混合型 メモリ C P U C P U 分散メモリ C P U C P U 通信 メモリ 通信 メモリ メモリ 4
並列化手法 種類 手法 利用 難度 共有メモリ コンパイラ OpenMP MPI 易中難 効果 低高高 今日のテーマ 分散メモリ MPI 難高 各手法は排他的なものではなく組み合わせが可能 5
OpenMP を使う OpenMP は共有メモリ型の並列計算機において 並列実行のためのアプリケーションプログラムインタフェース 当初 並列計算機を扱うベンダーが個々のコンパイラ (Fortran) のための指示子 ( 拡張仕様 ) を定義していた それが次第に標準化されたもの 参考 : https://computing.llnl.gov/tutorials/openmp/ テキスト :OpenMP Application Program Interface (pdf ファイル ) 6
Chapter 1: Hello world hello.f コンパイル リンク 実行特別なことはないが write(6,*) "Hello" write(6,*) " world!" stop end コンパイル リンク gfortran hello.f -o hello 実行 hello 結果 Hello world! 7
h1.f 早速 並列実行! コンパイル リンク gfortran -fopenmp h1.f -o h1!$omp parallel write(6,*) "Hello" 実行 h1 2 行追加 実習!$omp end parallel write(6,*) " stop end world!" 1. オプション -fopenmp なしでコンパイル 実行 結果 Hello Hello 注意 2. 環境変数の変更 ( 例 export OMP_NUM_THREADS=3) world!!$omp の他に c$omp,*$omp も可 C 言語では #pragma omp と記述する 8
h2.f プログラム内で並列数を設定する コンパイル リンク gfortran -fopenmp h2.f fo h2 1 行追加 実習 call omp_set_num_threads(3)!$omp parallel write(6,*) "Hello"!$omp end parallel write(6,*) " world!" stop end 1. オプション /Qopenmpなしでコンパイル!$ をつけてプログラムを一元化 2. 実行時に並列数を変更 h22.f h23.f 実行 h2 結果 Hello Hello Hello world! 9
プログラムの一元化 パラレル版 シリアル版などで 2 つのプログラムを維持管理するのは得策ではない OpenMP では OpneMP 機能を通常コメントとして扱うので問題なく一元化できる MPI 並列の場合 関数コールになるため一元化は難しい その場合 並列化に関わる部分をできるだけ局所化し その部分だけパラレル版 シリアル版に分け残りを一元化するべきである 10
1 行追加 実習 h3.f 役割分担のために 自分のスレッド 番号を得る use omp_lib!$omp parallel write(6,*) "Hello, omp_get_thread_num()!$omp end parallel write(6,*) " stop end world!" 1. 何度か実行する 番号の順序は常に一定か? 2. プログラムの 1 行目は何か 削除しコンパイル実行 ( コメント行にするために 1 カラム目に C をつける ) コンパイル リンク gfortran -fopenmp h3.f -o h3 実行 h3 結果 Hello 0 Hello 2 Hello 1 world! 11
1 行追加 h4.f スレッド番号を変数に格納大した変更ではないが何か変 use omp_lib!$omp parallel nth= omp_get_thread_num() write(6,*) "Hello, nth!$omp end parallel write(6,*) " world!" stop end コンパイル リンク gfortran -fopenmp h4.f -o h4 実行 h4 結果 Hello 2 Hello 2 Hello 2 world! 12
フォーク ジョイン フォーク スレッド スレッド スレッドメモリ nth ジョイン nth という変数領域は 1 つしかない そのため write 文の前に他のスレッドの代入文で値が書き換えられると意図した出力とはならない h44.f 時間 13
h44.f private 指定 コンパイル リンク gfortran -fopenmp h44.f -o h44 1 行追加 use omp_lib!$omp parallel private(nth) nth= omp_get_thread_num() write(6,*) "Hello, nth!$omp end parallel write(6,*) " world!" stop end 実行 h44 結果 Hello 0 Hello 2 Hello 1 world! 14
Chapter 2: Simple loop implicit double precision(a-h,o-z) character*10 buff allocatable a(:),b(:),c(:) 動的メモリ配列宣言 call getarg(1,buff) 実行時引数を取得 ( 問題サイズ ) read(buff,*) m 文字型で取得したものを整数に変換 allocate(a(m),b(m),c(m)) サイズに合わせて配列確保 t1=elaptime() ループ開始時刻の取得!$omp parallel do do 100 i=1,m 初期設定 a(i)=dble(i) ai=i b(i)=dble(i)*dble(i) bi=i^2 100 continue do 200 i=1,m 和の計算 c(i)=a(i)+b(i) ci=ai+bi 200 continue!$omp end parallel do t2=elaptime() ループ終了時刻 write(6,*) c(m/2),t2-t1 cの中央値と時間の出力 stop end コンパイル リンク ifort simple_s1.f elaptime.o -o s1 実行 s1 50000000 ( 零 7 個 ) 出力 625000025000000. 1.141000 15
Chapter 3 パイの計算 pi.f implicit double precision(a-h,o-z) character*10 buff data exact_pi/3.1415926535897932384d0 / call getarg(1,buff) read(buff,*) m width=1.0d0/dble(m) t1=elaptime() pi=0.0d0 do 100 i=0,m-1 x=(dble(i)+0.5)*width; pi = pi + 4.d0 *width/ (1.0d0+x*x) 100 continue t2=elaptime() write(6,*)" PI = ",pi write(6,*)" time ",t2-t1 write(6,*) "error =",exact_pi-pi stop end 4 1 x 1 0 2 dx 1 f ( x) 1 x 区分求積法 2 コンパイル リンク Ifort pi.f elaptime.f /o pi 実行 Pi 100000000( 零 9 個 ) 結果 PI = 3.14159265358987 time 5.93000173568726 error = -7.904787935331115E-014 16
p1.f とりあえず並列化 implicit double precision(a-h,o-z) character*10 buff data exact_pi/3.1415926535897932384d0 / call getarg(1,buff) read(buff,*) m width=1.0d0/dble(m) t1=elaptime() pi=0.0d0!$omp parallel do do 100 i=0,m-1 x=(dble(i)+0.5)*width; pi = pi + 4.d0 *width/ (1.0d0+x*x) 100 continue!$omp end parallel do t2=elaptime() write(6,*)" PI = ",pi write(6,*)" time ",t2-t1 write(6,*) "error =",exact_pi-pi stop end コンパイル リンク gfortran -fopenmp p1.f elaptime.f -o p1 実行 p1 50000000 ( 零 7 個 ) 結果 PI = 0.979914652507533 time 0.328000068664551 error = 2.16167800108226 全然違う 17
Private の使い方 p2.f ループ内の変数をプライベート化 xのプライベート化は必要 widthのプライベート化は不要 ( 初期値を使うだけなので ) piについては工夫が必要 p3.f 各スレッドでの計算を確認 それぞれの pi を合計すると正解 p4.f reduction を使う (private の特殊ケース ) 18
private の使い方 内容 private firstprivate copyin lastprivate copythread reduction 初期値 終値についての処理なし 並列ループに入る際 その変数の値を各スレッドに代入 threadprivate の変数に対して上記の処理を行う 並列ループを出る際 loop の最終値を逐次実行部に代入 end single 文に現れるもので 全スレッドにブロードキャスト 並列ループを出る際 リダクションを行う reduction(op: 変数 list) の形式 op と初期値は下記 Op + * -.and..or. Max Min 初期値 0 1 0 1 0 1 0 1 19
Chapter4: 級数の和で得られる関数の区分求積法 10 0 e x dx x0 面積 = 底辺 高さ (e x0 ) k x0 x0 高さを e として計算する k! 20
重みが異なるタスクの処理 - 均等分割ではバンランスがとれない e x k x 級数の和 k! で評価するとxが大きいほど収束が悪くて処理時間がかかる 均等分割すると右の方ほど処理時間がかかる 分割した区間を各プロセスでラウンドロビンで担当する gfortran fopenmp e4.f elaptime.o o e4./e4 10000000 ( 零 7 つ )!$omp do schedule(static,10) を入れる OMP_NUM_THREADS=2 OMP_NUM_THREADS=4 Schedule 句を入れる 21
Schedule 句を入れたコード!$omp parallel private(x) reduction (+:sum)!$omp do schedule(static,10) do 100 i=0,m-1 x=(dble(i)+0.5)*width; sum = sum + width*qexp(x) 100 continue!$omp end do write(6,*) omp_get_thread_num(),sum!$omp end parallel 22
SCHEDULE 句の種類 Schedule 句内容使用例 static dynamic guided runtime 一定のブロックサイズを順次実行 一定のブロックサイズを空きがあるスレッドで実行 最初は大きなサイズで実行し 最終的に指定値になる 環境変数 OMP_SCHEDULE から得る Schedule (static,10) Schedule (dynamic,10) schedule (guided,10) Schedule (runtime) 各指定値を chunk size という 23
Schedule の方法の選び方 各反復での作業量が一定なら指定なし または static がよい 各反復の作業量変動が大きい時は dynamic 指定する それでもバランスが悪い場合は chunk size を小さくする 小さな処理が多量にある時はオーバーヘッドを減らすために guided を用いる 24
Chapter 5 素数の計算 prime.f ( 作業量が各反復で不規則に大きく異なる例 ) 1 億 1(100000001) から 100 個の奇数について素数の判定を行う コンパイル 素数の判定については決して よいアルゴリズムを用いているわけではない gfortran prime.f elaptime.o -o prime 実行 prime chart2 作業量の変動が見られる 25
p1.f 並列化する コンパイル gfortran -opnemp p1.f elaptime.o 実行 p1 -o p1 ループの反復数 ( ここでは 100) を均等分割し 並列実行している しかし一方に作業量の大きいところがあると 終了時刻に差ができる 効率が落ちる 26
Schedule 句による調整 ( チューニング ) 実習 p2.f を用いて schedule chunk size を変更し時間測定する dynamic,20 時間 ( 秒 ) static,20 時間 ( 秒 ) static,10 static,5 static,1 static dynamic,10 dynamic,5 dynamic,1 dynamic guided,20 guided,5 1CPU 時間 1 2CPU 時間 2 ( 上記の最高の値 ) スピードアップ率 3 (1/2) 並列数 4 並列化効率 5 実習 p21.f を用いて環境変数にて schedule,chunk size の設定を行う 例 :set OMP_SCHEDULE= DYNAMIC,10 27
同期について p3.f 並列化した Do ループを抜けた時 前プロセスの終了を待って次の実行が開始される 28
同期を制御する種々の命令 構文 内容 nowait!$omp end do nowait 暗黙にあるバリアを無効にする barrier!$omp barrier すべてのスレッドの同期をとる single!$omp single 1つのスレッドだけが実行する nowaitがないと同期がとられる copyinにて初期設定 master!$omp master マスタースレッドのみ実行する 同期はとらない critlcal!$omp critical 同時に実行するスレッドを1つに 制限する atomic!$omp atomic 次の行の実行をスレッド1つに 制限する p31.f 参照 oedered!$omp orderd 逐次実行と同じ順序で実行する 29
p31.f nowait の使い方 この空白が問題!$omp end do!$omp end do no wait 30
Chapter 6 ランタイム環境ルーチン env.f omp_set_num_threads() スレッド数を設定 omp_get_num_threads() 現在のスレッド数を取得 omp_get_thread_num() 自身のスレッド番号を取得 omp_get_max_threads() 利用可能な最大スレッド数 omp_in_parallel() 並列領域か否か ( 論理値 ) omp_get_num_procs() プロセッサ数を取得 コンパイル ifort /Qopenmp env.f /o env 実行 env 結果 out parallel max_threads 2 num_proc 2 in parallel F hello 0 in parallel max_threads 2 num_proc 2 in parallel T hello 1 in parallel max_threads 2 num_proc 2 in parallel T 31
ビジュアルテクノロジー事業内容 HPC 向け高速性能コンピュータの製造 販売および保守サポート HPC 向け各種サービス プログラムの並列化 最適化サービス(Fortran/c) FEMによるCAE 受託解析サービス OpenFOAM 技術支援 受託解析サービス HPCクラウドコンピューティングサービス 運用支援サービス 生産 キッティング受託サービス ファシリティサービス 32
ネットワーク CPU- メモリ GPU,Co-processer MPI OpenMP コンパイラー OS ユーティリティ 数値計算ライブラリ 流体 - 構造解析 最適化 流体 - 構造解析 最適化 HPC コンシェルジュハードウェアシステムソフトアプリケーション開発数値計算ライブラリエンドユーザー強度計算解析サービス建築 土木 機械 医療流体 構造アプリインストールアプリチューニング VT の HPC サービス