高速化チューニング 2016年度秋版 2.8(2016.10.1) 東京工業大学 学術国際情報センター 1 目 次 準 備 高速化チューニング チューニング概要 ・・・・・・・・・・・・・・ チューニングとは 最適化 ・・・・・・・・・・・・・・・・ 最適化のための記述 阻害要因の除去 ・・・・・・・・・ 高速化阻害要因の除去 キャッシュチューニング ・・・・・・・・ 高速化のための手法 並列プログラムによる高速化 並列化概要 ・・・・・・・・・・・・・・・・・・・・・・・・ 並列化とは SMP並列プログラム ・・・・・・・・・・・ 共有メモリ型並列手法 MPI並列プログラム ・・・・・・・・・・・・・ 分散並列化手法 性能評価 (共通) 性能評価 ・・・・・・・・・・・・・・・・ コストの把握と計測 この資料は http://tsubame.gsic.titech.ac.jp/docs/guides/UNIX/tune.pdf 2 共通 準 備 Win/Macからの利用 コンパイラのマニュアル 練習プログラム TSUBAME2.5利用の手引 http://tsubame.gsic.titech.ac.jp/docs/guides/tsubame2/html/index.html もご参照ください 3 Windows端末からの利用 Xwin Serverのアイコンをクリック Serverのアイコンをクリック cygwinのアイコンをクリック cygwinのアイコンをクリック ssh -Y -l login_name loginlogin-t2.g.gsic.titech.ac.jp startx windows用の2ボタンマウスの操作 (ホイールつき) 中ボタンクリック時は、ホイールを軽く押す ホイールを回せばスクロール可能 使用例(メニューの表示) コントロール+中ボタンでメニューを表示 スクロールバーをチェック 使用例(ペースト) 左ボタンで選択(色が反転:コピー) ホイールを軽く押す(ペースト) 4 Macからの利用 ターミナルの設定で、 3ボタンエミュレートを選択 こちらでもほぼ同じ ※control、あるいは、contol+optionを押しながらマウスをクリックでメニュー 5 修正 コンパイラのマニュアル ユーザーズマニュアルの利用方法 > source /usr/apps.sp3/ usr/apps.sp3/isv /apps.sp3/isv/ isv/pgi/pgiset16.9.sh pgi/pgiset16.9.sh (最新のコンパイラを使う場合) > which pgf95 /usr/apps.sp3/ usr/apps.sp3/isv /apps.sp3/isv/ isv/pgi/16.9/linux86 pgi/16.9/linux86/16.9/linux86-64/2016/bin/pgf95 > cd /usr/apps.sp3/ usr/apps.sp3/isv /apps.sp3/isv/ isv/pgi/16.9/linux86 pgi/16.9/linux86/16.9/linux86-64/2016/ 64/2016/ > cd doc > ls editline_license.txt LICENSE_LLVM.txt pgiugpgiug-x64.pdf LICENSE_JRE.txt LICENSE_OPENACC_UM_BETA.txt LICENSE_LAPACKE.txt LICENSE_ScaLAPACK.txt SUBSCRIPTION_SERVICE > evince pgiugpgiug-x64.pdf <-- 直接参照 ※pgi16版より、マニュアルはhttp://www.pgroup.com/resources/docs.htm ※X-Windowで利用、同様の手順にてintelも参照できます。 (/usr/apps.sp3/isv/intel/2015.0.090/composer_xe_2015/Documentation/en_US) 6 修正 練習用サンプルプログラム 場所 : /work1/soudan/tune check01.f check02.f check02a.f check03.f check04.f check04a.f check05.f check05a.f check06.f check08.f cpp.inc cuda/ mpi_go NaNck.f90 openmp.c openmp.f sample.F 配列外参照のプログラム 誤差の出るプログラム 同、修正例 pfiオプション利用例 自動並列不可プログラム 同、並列化例 インライン展開用(自動) インライン展開用(手動) 収集・拡散 コンパイラで結果が違う例 cpp利用例 GPU用pgi対応プログラム 並列プロファイラの例(シェル) NaNの出るサンプル openMPの簡単な例(C) openMPの簡単な例(fortran) cpp利用のもと sample2.f sample2a.f sample3.f sample4.f90 sample5.f sample5.c sample5a.f sample6.f sub05.f wclock.c README sample10.f sample10.c sample10k.f sample11.f sample11.c mpi2.f90 アンロール、その他 手動アンロール ブロック化 プロファイラ用 MPI練習用 MPI練習用 同、ノード制限なし ループの最適化 インライン展開用 計測用時計ルーチン 利用解説 チューニング例題(実習) 同 C言語 同 GPU高速化事例 並列化例題(実習) 同 C言語 MPI2規格の試験 ※自分の作業ディレクトリ(/work1/group/login_name/)にコピーして利用 7 高速化チューニング チューニング概要 ・・・・・・・・・・・・・・ チューニングとは 最適化 ・・・・・・・・・・・・・・・・ 最適化のための記述 阻害要因の除去 ・・・・・・・・・ 高速化阻害要因の除去 キャッシュチューニング ・・・・・・・・ 高速化のための手法 性能評価 ・・・・・・・・・・・・・・・・ コストの把握と計測 8 共通 チューニング概要 チューニングの流れ コンパイル ライブラリのリンク コンパイルオプション プログラムの実行 バッチジョブ 性能解析ツールの使用 結果の検証 9 チューニングの流れ プログラム動作確認 CPU時間を集中的に消費する部分を検出 検出部分を調査 I/O FLOPS time GPU化の検討/プログラムの改良 No ●gprof pgprof vtune ●阻害要因の排除 ●キャッシュチューニング ●ループの最適化 注意点 十分な性能が出たか? yes 終了 開始時のプログラムは保存 ひとつ前に戻れること ※この資料ではGPU化は扱いません 化は扱いません この資料では 10 コンパイラ 利用できるコンパイラ コンパイラ名称 gnu FORTRAN C , C++ MPI利用 openMP 自動並列 高速オプション リスト出力 GPU対応 gfortran gcc,g++ ○ -fopenmp × --Wall (※) pgi pgf95 pgcc,pgc++ ○ -mp -Mconcur -fastsse -Minfo=all pgfortran 注意点 1. TSUBAMEの のMPI標準コンパイラは 標準コンパイラはintelです。 です。(14.0.2.144) 標準コンパイラは です。 2. 切り替えにより gnu , pgi のMPIも利用できます も利用できます ※ nvccでコンパイルしたオブジェクトをリンク でコンパイルしたオブジェクトをリンク intel ifort icc,icpc ◎ -openmp -parallel -fast -vec-report2 (※) mpi標準コンパイラ 11 修正 コンパイル(基本型) 例1. Fortranプログラム 「sample.f」 の場合 $ pgf95 -o sample sample.f $ ifort -o sample sample.f $ gfortran -o sample sample.f 例2. Cプログラム 「sample.c」 の場合 $ pgcc -o sample sample.c $ icc -o sample sample.c $ gcc -o sample sample.c ※最新版のコンパイラを使いたい場合は TSUBAME2.5利用の手引 TSUBAME2.5利用の手引 http://tsubame.gsic.titech.ac.jp/docs/guides/tsubame2/html/programming.html#id2 「6.1.5 違うバージョンのコンパイラ」 を参照ください pgiコンパイラの例 pgiコンパイラの例 $ source /usr/apps.sp3/isv/pgi/pgiset16.9.sh 12 コンパイル(SMP並列) 例3. OpenMP Fortranプログラム 「sampleOMP.f」 の場合 $ gfortran -O3 -o sampleOMP -fopenmp -Wall sampleOMP.f $ pgf95 -fastsse -o sampleOMP -mp -Minfo=all sampleOMP.f $ ifort -fast -o sampleOMP -openmp -openmp-report2 sampleOMP.f 例4. 自動並列「sampleAUTO.f」 の場合 $ pgf95 -fastsse -o sampleAUTO -Mconcur -Minfo=all sampleAUTO.f $ ifort -fast -o sampleAUTO -parallel -par-report2 sampleAUTO.f ※ifortで「 ※ifortで「V で「V , U キュー」利用時は “-fast”は使用しない fast”は使用しない 例5. pthread Cプログラム 「sample.c」の場合 $ pgcc -o sample sample.c -lpthread 13 修正 コンパイル(MPI並列) 例6. MPI Fortranプログラム 「sampleMPI.f」 の場合 $ mpif90 -o prog sampleMPI.f (intel) $ source set_set_ompi-1.6.5_p16.1.sh $ mpif90 -o prog sampleMPI.f $ source set_ompi-1.6.5_g4.3.4.sh $ mpif90 -o prog sampleMPI.f (このシェルによりコンパイラをpgiに切り替え) (このシェルによりコンパイラをgnuに切り替え) 例7. MPI環境の切り替え > > > > > > > > > source source source source source source source source source set_ompi-1.6.5_i2013.1.046.sh set_ompi-1.6.5_p16.1.sh set_ompi-1.6.5_g4.3.4.sh set_mvp-2.0rc1_i2013.1.046_cuda7.5.sh set_mvp-2.0rc1_p16.1_cuda7.5.sh set_mvp-2.0rc1_g4.3.4_cuda7.5.sh set_mpch-3.1_i2013.1.046.sh set_mpch-3.1_p16.1.sh set_mpch-3.1_g4.3.4.sh ---------- デフォルト(openmpi + intel) openmpi + pgi openmpi + gcc mvapich2 + intel mvapich2 + pgi mvapich2 + gcc mpich2 + intel mpich2 + pgi mpich2 + gcc 14 ライブラリのリンク リンク時にオプションでライブラリ名を指定 → アーカイブファイルを検索してリンク lib***.a というライブラリをリンクする → -l*** とオプションの直後に記述 例1. Fortran,Cプログラムに Fortran,Cプログラムに「 プログラムに「libm. libm.a」をリンクする場合 $ pgf95 sample.f –lm $ pgcc sample.c –lm 例2. 特定の場所にあるlibblas 特定の場所にあるlibblas. libblas.aをリンクする場合 $ pgf95 sample.f –L/opt/lib -lblas 例3. MKLなどの数値計算ライブラリを使用する場合 MKLなどの数値計算ライブラリを使用する場合 $ ifort -o sample -mkl=sequential sample10.f wclock.o -lmkl_lapack95_lp64 ¥ -L/usr/apps.sp3/isv/intel/xe2013.1.046/composer_xe_2013_sp1.2.144/mkl/lib/intel64 ※PGIの ※PGIのACMLは廃 ACMLは廃止されました は廃止されました。 止されました。 ※使用したいライブラリがTSUBAME2.5 使用したいライブラリがTSUBAME2.5にない場合は TSUBAME2.5にない場合は ご自身のディレクトリにインストールをトライしてみてください 15 プログラムの実行 例1. Fortranプログラム 「sample」 を1CPUで実行する場合 $ ./sample 例2. MPIの実行ファイル「sample」を2CPUで実行する場合 $ mpirun -np 2 ./sample 例3. OpenMPの実行ファイル「sample」を2CPUで実行する場合 $ export OMP_NUM_THREADS=2 $ ./sample 例4. pthreadの実行ファイル「sample」を2CPUで実行する場合 $ export NCPUS=2 $ ./sample ※この形の実行は試験のみにしてください 16 プログラムの実行(バッチ実行) t2sub -g 課金グループ番号 -q 投入先キュー ジョブスクリプト ※詳細は「TSUBAME2.5 TSUBAME2.5利用の手引き TSUBAME2.5利用の手引き」を参照 利用の手引き http://tsubame.gsic.titech.ac.jp/docs/guides/tsubame2/html/index.html 例1. 単一ジョブ $ t2sub -N test_job -q S ./sample.sh (お試しキュー) 例2. 並列ジョブ(OpenMP, ptheread) OpenMP,スレッドなどを使用し、4CPU使用したい場合 $ t2sub –l select=1:ncpus=4 –q S ./sample_parallel.sh 例3. 並列ジョブ(MPI) MPIを使用し、4CPU使用したい場合 $ t2sub –l select=1:ncpus=4:mpiprocs=4 –q S ./sample_mpi.sh ※メモリをギガ単位で使用する場合は –mem を必ずつけてください (2.5Gbyteを使う場合には select=1:mpiprocs=4:mem =2500mb などとします) 17 バッチジョブ フォアグラウンド ジョブ ノード確保のための ダミージョブ投入 t2del ssh直接ログイン ユーザー 確認(t2stat) (ダミージョブ) 強制終了 t2sub コマンド t2del バッチジョブ 18 修正 性能解析ツールの使用 PGPROF(プロファイリング機能)の使用 関数・サブルーチン、行単位での計算コストの計測 手順1. Fortranプログラム のコンパイルとリンク $ source /usr/apps.sp3/isv/pgi/pgiset15.10.sh $ pgf95 –Mprof=func –o sample4 sample4.f90 (または –Mprof=lines) 手順2. プログラムの実行 $ ./sample4 ※後半で実際に使用してみます 手順3. PGPROFの起動 $ pgprof –nocheckjvm -jarg,-Xmx4G (注意:このjobはX-Window) 処理が重いので、ssh接続で-Cオプションをつけたほうが良い 19 結果の検証 (check02) 最終結果がどれであるのかを把握 結果の変化が許容範囲内であるのか? cat check02.f real a a=0.0 do i=1,30 do j=1,100000 a=a+0.000001 enddo enddo write(6,*) a stop end サンプルの プログラム名 以後同様です > pgf95 -o check02 check02.f > ./check02 2.916388 FORTRAN STOP > pgf95 -fastsse -o check02 check02.f > ./check02 3.000393 FORTRAN STOP > pgf95 -o check02 -r8 check02.f > ./check02 3.000000000065429 FORTRAN STOP real*8 a a=0.0 do i=1,30 do j=1,100000 a=a+1.0D-6 enddo enddo write(6,'F12.7') a > ./check02a 3.0000000 FORTRAN STOP 20 最適化 言語/コンパイル ソフトウェアパイプライン 最適化(オプション) ループの最適化 アンロール インライン展開 指示行 分岐確率 21 コンパイルオプション(PGIの例) 最適化レベル(-O) コード最適化レベルを指定する。(0、1、2、または 3)。 • 0: 各ステートメントに対し基本ブロックを生成する。 スケジューリング並びにグローバルな最適化は行わない。 • 1: 基本ブロック内でのスケジューリング並びにいくつかのレジスタ・割当に 関する最適化を行う。しかし、グローバルな最適化は行わない。 • 2: レベル 1 の最適化を行う。さらに、導入変数の削除や問題のない ループの移動等のグローバル最適化を行う。 • 3: レベル 1, 2 の最適化だけでなく、効果のあるなしに関わらず、スカラの 置き換えなど、より積極的な最適化を行う。 高速化(-fastsse) SSE/SSE2を有するx86 用のオプション(pgi) -O2 -Munroll -Mnoframe -Mscalarsse -Mvect=sse -Mchache_align -Mflushz と同等 ※結果が変化しないのであれば、このオプション(fastsse)をお勧めします。 をお勧めします。 結果が変化しないのであれば、このオプション 22 コンパイルオプション(2) コードの最適化(-tp) プロセッサのタイプを指定し、アーキテクチャに沿ったコードを生成する。 ターゲットの default は、インストールしたマシン自身が設定されている。 • • • • • 32 ビットの Pentium 4 プロセッサでは-tp p7 Pentium Pro/II/III プロセッサでは-tp p6 Pentiumプロセッサでは-tp p5 Intel i7系は、-tp nehalem-64 -tp nehalem-32 を指定した場合は、32bit コードが生成 インテルの64bit Xeon EM64T プロセッサに対しては、 -tp p7-64 を指定 最適化フラグ(-M) -Minfo=all 等の形で多くのオプションを指定できる 23 言語による記述の差 C言語 for (kk = 0; kk < kkmax ; ++kk) { for (hh = 0; hh < hhmax ; ++hh) { abc[kk][hh] = fortran do kk=0,kkmax-1 do hh=0,hhmax-1 abc(hh,kk) = 再内側はhhとなる。 コンパイル方法(混合) pgcc cmain.c pconf.o -pgf90libs pgf90 fmain.f pconf.o pgf95 -o sample sample2.f wclock.c 24 CとFortranの結合 配列の次元並びが逆 fortranのルーチンは後ろに"_"がつく : function ppcntf(val1,val2,val3,qq,rr,bitpint,wkk,b,cc) long wkk[16][512]; integer popcntf int v1,v2,v3 ; integer FX /Z'FFFF'/ : integer*8 wkk(0:511,0:15) for (jj = jstart; jj < jend; ++jj) : { do j=0,15 for (hh = 0; hh < wordcnt; ++hh) do i=0,510 { b(i) = b(i) + cc(i,ishft(wkk(i,j),FX)) for (kk = 0; kk < bitnum; ++kk) end do { end do wkk[hh][kk] = Ax[ii][hh] ^ BBx[kk][hh][jj]; : } popcntf = con } return } end i_qq = (int)qq; i_bitnum = (int)bitnum; ret = ppcntf_(&v1,&v2,&v3,&i_qq,&i_rr,&bitpint,wkk,corv,bitcnt); if (ret != 0) break; : 25 コンパイル情報 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 REAL*8 A(5000,600),B(600,5000) REAL*8 US,sum real*8 second real*8 t0,t1,r2 t0=second() us = 10.0 do j=1,600 do i=1,5000 b(j,i) = float(j+i)/1.0D+1 enddo enddo DO k=1,10 DO J = 1,600 DO I = 1,5000 A(I,J) = B(J,I) + us (sample2) 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ENDDO ENDDO us = us + 1.2 ENDDO sum=0.0 do j=1,600 do i=1,5000 sum=sum+A(i,j) enddo enddo t1=second() t2 = t1 - t0 write(6,*) ' TIME= ',t2,sum stop end > pgf95 -fastsse -o sample2 -Minfo=all sample2.f wclock.c 8, Loop interchange produces reordered loop nest: 9,8 Generated vector sse code for the loop 9, Loop not fused: different loop trip count 13, Loop not fused: different loop trip count 15, Generated vector sse code for the loop 22, Loop not fused: function call before adjacent loop 23, Generated vector sse code for the loop Generated a prefetch instruction for the loop 26 コンパイル情報(2) 主なメッセージの見方 (sample2) xx , xxxxx-xxxxxx;xx ライン番号 , 実行内容 ループ分割した場合のメッセージ Distributing loop; 2 new loops ループの入れ替えをした場合のメッセージ Loop interchange produces reordered loop nest: 10,9 アンロールをかけた Unrolled inner loop 4 times ベクトル化した Generated vector sse code for inner loop プリフェッチを入れた インライン展開 並列化 Generated a prefetch instruction for the loop sub05 inlined, size=11, file sub05.f Parallel code generated with block distribution ※メッセージは同じコンパイラでもバージョンにより異なる場合があります 27 計算式とコンパイルオプション 計算式と高速化オプションによる処理時間(秒) 式 なし -fastsse -O3 b = a**3.1 3.8878419 1.2859628 1.3149149 b = a**3 0.4918699 0.4741799 0.4733250 b = a*a*a 0.4991550 0.5176761 0.4905760 b = a **0.5 0.5227251 0.5176761 0.4931900 b = sqrt(a) 0.5848169 0.4834938 0.5019688 ※判別のために繰り返しによる累積です(PGI) 判別のために繰り返しによる累積です 28 ソフトウェアパイプライン 処理イメージ DO i = 1, n A A(i) = E(i) * D(i) B B(i) = A(i) + F(i) C C(i) = B(i) - C(i) D D(i) = A(i) / D(i) ENDDO 通常の実行 i=1 A i=2 i=3 i=4 B C D A B C D A B C D A B C D ソフトウェアパイプライン実行 i=1 i=2 i=3 i=4 A B C D A B C D A B C D A B C D 29 最適化オプションで実施される項目(0) コピー伝搬 X = A Y = 5.0 + X Yの計算にXが必要 X = A Y = 5.0 + A Xの値を待たずにYを決定できる プログラム全体を見渡して定数を見つける(定数のたたみこみ) PROGRAM MAIN INTEGER M,N PARAMETER (N = 1024) M = 256 J = M + N END Jも定数として扱う 30 最適化オプションで実施される項目(1) 演算コストの軽減 時間のかからない計算に変える A = B**2 J = L*2 Y = X/4.0 A = B*B J = L + L Y = X*0.25 変数のリネーム 同じ変数x(同一記憶場所)が別の用途に使用されている →レジスタ待ちの事態が発生する可能性がある X = A * B Q = C + X + X X = Y + Z X0= A * B Q = C + X0 + X0 X = Y + Z 31 冗長コードの除去 決して実行されない命令 命令の結果がどこにも使用されない命令 main() { int i,k; i=k=1; i+=1; k+=2; printf("%d¥n",i); } kの計算は最終結果に なんら影響を与えない PROGRAM MAIN I = 2 WRITE(*,*) I STOP I=4 WRITE(*,*) I END STOP以降は実行されない コンパイラによっては警告を出して切り捨てる 32 帰納変数の単純化 K = M DO 10 I=1,N K = K+4 10 CONTINUE DO 10 I=1,N K = I*4 + M 10 CONTINUE 仮にN=10,M=5 として、結果を追ってみる I 1 2 3 4 5 6 7 8 9 10 左のループ 1*4+5 = 9 2*4+5 = 13 3*4+5 = 17 4*4+5 = 21 5*4+5 = 25 6*4+5 = 29 7*4+5 = 33 8*4+5 = 37 9*4+5 = 41 10*4+5 = 45 右のループ 5+4 = 9 9+4 = 13 13+4 = 17 17+4 = 21 21+4 = 25 25+4 = 29 29+4 = 33 33+4 = 37 37+4 = 41 41+4 = 45 33 ループの最適化 ループ分割 DO I = 1, N C(I) = 1.0 DO J = 1, M A(I,J) = A(I,J) + B(I,J) * C(I) ENDDO ENDDO DO I = 1, N C(I) = 1.0 ENDDO DO J = 1, M DO I = 1, N A(I,J) = A(I,J) + B(I,J) * C(I) ENDDO ENDDO (sample6) ループ融合 DO I = 1, N A(I) = B(I) + C(I) ENDDO DO J = 1, N IF(A(J) .LT. 0) A(J) = 0.0 ENDDO DO I = 1, N A(I) = B(I) + C(I) IF(A(I) .LT. 0) A(I) = 0.0 ENDDO 34 アンロール(自動) 7 8 9 10 11 12 (sample2) us = 10.0 do j=1,600 do i=1,5000 b(j,i) = float(j+i)/1.0D+1 enddo enddo > pgf95 -o sample2 -Minfo=all –Munroll sample2.f wclock.c sample2.f: fastsseを使うと を使うとunroll しない を使うと MAIN: 9, Loop unrolled 4 times 15, Loop unrolled 8 times アンロールをしたメッセージ 23, Loop unrolled 4 times wclock.c: > ./sample2 TIME= 5.130618095397949 1586700028.467178 FORTRAN STOP 35 アンロール(手動) DO J = 1,600 DO I = 1,5000 A(I,J) = B(J,I) + us ENDDO ENDDO (sample2a) アンロールの段数の指定 DO J = 1,600,4 (例は4段) DO I = 1,5000 A(I,J) = B(J,I) + us A(I,J+1) = B(J+1,I) + us A(I,J+2) = B(J+2,I) + us A(I,J+3) = B(J+3,I) + us ENDDO ENDDO 段数分記述 > pgf95 -fastsse -o sample2a -Minfo=all sample2a.f wclock.c > ./sample2a (なし) 注意) 配列のサイズを超えない TIME= 5.130618095397949 1586700028.467178 注意) ループが奇数の場合は注意 > ./sample2a (2段) TIME= 4.249938964843750 1586700028.467178 結果を確認しながら試 して > ./sample2a (4段) みるのがよい TIME= 2.480340003967285 1586700028.467178 > ./sample2a (8段) 割り切れない TIME= 2.294842958450317 1586700028.467178 配列外参照が発生している > ./sample2a (16段) が動く場合もあるので注意 TIME= 2.323076009750366 1605556810.049946 36 インライン展開(1:手動) 呼び出し回数が多いほど有効 $ cat sub05.f SUBROUTINE SUB05(A,B,N,M,US) INTEGER N,M REAL*8 A(N,M),B(M,N),US DO i = 1,N DO j = 1,M A(I,J) = B(J,I) + us ENDDO ENDDO RETURN END サブルーチンを展開 DO K=1,100 ! CALL SUB05(A,B,N,M,US) DO i = 1,N DO j = 1,M A(I,J) = B(J,I) + us ENDDO ENDDO US = US + 1.2 ENDDO $cat check05.f REAL*8 ,ALLOCATABLE :: A(:,:),B(:,:) real*8 second real*8 US,t0,t1,r2 US = 0.0 N=5000 M=600 ALLOCATE ( A(N,M) ) ALLOCATE ( B(M,N) ) do j=1,M do i=1,N B(j,i) = 1.0 enddo enddo t0=second() DO K=1,100 CALL SUB05(A,B,N,M,US) US = US + 1.2 ENDDO t1=second() t2 = t1 - t0 sum=0.0 do j=1,M do i=1,N sum=sum+A(i,j) enddo enddo write(6,*) ' TIME= ',t2,sum DEALLOCATE ( A ) DEALLOCATE ( B ) stop end 37 インライン展開(2:自動) (check05) > pgf95 -o check05 -Minfo=all check05.f sub05.f wclock.c > ./check05 TIME= 8.233783006668091 4.7999462E+09 FORTRAN STOP > pgf95 -fastsse -o check05 -Minfo=all check05.f sub05.f wclock.c > ./check05 サブルーチンの展開を明示的に指示 TIME= 6.728569030761719 4.7999462E+09 FORTRAN STOP > pgf95 -fastsse -o check05 -Minfo=all -Minline=reshape check05.f sub05.f wclock.c : 16, sub05 inlined, size=11, file sub05.f (1) インラインのメッセージ 16, Loop unrolled 20 times (completely unrolled) : > ./check05 TIME= 1.346210956573486 4.7999462E+09 FORTRAN STOP –Minline=levels:10 -Minline=no などと使う 38 指示行 (sample6) pgi/intelコンパイラではコンパイラ指示行が使用できる 主な指示行とその効果(intelコンパイラの例) DO K=1,200 ベクトル化しない cDEC$ novector DO J = 1,600 pgiの場合は C(J)= float(J)/10.0 cpgi$l novector ENDDO DO J = 1,600 DO I = 1,5000 A(I,J) = B(I,J)*C(J) + us ENDDO : ※ベクトル=ソフトウェアパイプライン ベクトル ソフトウェアパイプライン と考えてください vector/novector unroll/nounroll 詳細はマニュアル参照 > ifort -vec-report2 sample6.f wclock.o : sample6.f(18): (col. 10) remark: loop was not vectorized: #pragma novector used. : C言語の場合は #pragma novector などと記述する。 39 分岐確率 (check03) プロファイラデータを利用して、分岐確率を持ち込む if文等が多い場合に高速化に働く > pgf95 -fastsse -Mpfi -o check03 check03.f wclock.o > ./check03 TIME= 0.5419440 1612.500000000000 --> pgfi.out > pgf95 -fastsse -Mpfo -o check03 check03.f wclock.o > ./check03 TIME= 0.4071510 1612.500000000000 40 コンパイラによる差(fortran) a=1.5 b=1.5 call datain call check(b) c=a+b write(6,*) c stop end subroutine datain real a,c a=0.5 return entry check(c) c=c+a return end > ifort check08.f > ./a.out 3.000000 > pgf95 check08.f > ./a.out 3.500000 FORTRAN STOP (check08) Call datainをコメントアウト > ifort check08.f > ./a.out 3.000000 > pgf95 check08.f > ./a.out 4.500000 FORTRAN STOP 同じ結果が出るように修正するには? subroutine datain real a,c data a /0/ save a a=0.5 return entry check(c) c=c+a return end pgi用に初期値をセット intel用にsave属性を付加 41 コンパイラによる差(C) (sample2c) 最近はC++のように記述する場合も増えています for (int k=0;k<200;k++) { for (int j =0;j<m;j++){ for (int i =0;i<n;i++){ a[i][j] = b[j][i] + us } } us = us + 1.2 ; } sum=0.0 ; ; icc -std=c99 sample2c.c gcc -std=c99 sample2c.c -std=c99 > icc sample2c.c sample2c.c(15): error: expected an expression > gcc sample2c.c sample2c.c:15: error: ‘for’ loop initial declaration used outside C99 mode > pgcc sample2c.c 42 阻害要因の除去 高速化阻害要因 間接メモリ参照 計算量の削減 不変式の移動とループ交換 本当に速くなる? ループ内の条件分岐 I/O 43 高速化阻害要因 オーバーへッドによる要因 サブルーチンコール(インライン化) 間接メモリ参照 ループ内の条件分岐 冗長な条件分岐 型変換 (real*4とreal*8が同じ式ででてくる) 不要な予約変数 EQUIVALENCE文の使用 ※高い最適化レベルではこれらの除去が積極的にトライされます。 44 間接メモリー参照 データの格納位置を別の配列に格納 1 2 3 4 1 2 4 10 5 6 7 8 9 10 11 12 13 ..... B A 6 8 DO i=1,100 indx=a(i) ans=B(indx) ENDDO 12 14 ・ ・ ・ C(i)=B(a(i)) などと配列をのせかえる (繰り返しが多い場合に有効) 45 計算量の削減 共通部分式の削除 c = a + b + d e = q + a + b temp = a + b c = temp + d e = q + temp コードの移動 DO 10 I=1,N A(I)=A(I)/SQRT(X*X+Y*Y) 10 CONTINUE TEMP=1/SQRT(X*X+Y*Y) DO 10 I=1,N A(I)=A(I)*TEMP 10 CONTINUE 46 不変式の移動とループ 交換 ループ不変条件 DO 10 I=1,N IF(L.EQ.0)THEN A(I)=A(I)+B(I)*C ELSE A(I)=0.0 ENDIF 10 CONTINUE IF(L.EQ.0)THEN DO 10 I=1,N A(I)=A(I)+B(I)*C 10 CONTINUE ELSE DO 11 I=1,N A(I)=0.0 11 CONTINUE ENDIF ループ交換 DO I = 1, N DO J = 1, M A(I,J)=B(I,J)+C(I,J) ENDDO ENDDO DO J = 1, M DO I = 1, N A(I,J)=B(I,J)+C(I,J) ENDDO ENDDO 47 本当に速くなる? 型変換 REAL*8 A(5000,600) REAL*8 B(600,5000) DO K=1,10 DO J = 1,600 DO I = 1,5000 A(I,J) = B(J,I) + us ENDDO ENDDO US = US + 1.2 ENDDO 2.807072 (sample2) REAL*4 B(600,5000) 型変換よりもキャッシュonの効果が出た ただし、誤差が大きくなる 2.610443 秒 式の移動 DO K=1,10 DO J = 1,510 DO I = 1,10010 A(i,j) = B(i,j)+US + sqrt(ab+cc) ENDDO ENDDO us = us + 12.5 ENDDO 2.806312 delt = sqrt(ab+cc) DO K=1,10 DO J = 1,510 DO I = 1,10010 A(i,j) = B(i,j)+US + delt ENDDO ENDDO us = us + 12.5 ENDDO 2.749396 48 ループ内の条件分岐 インデックス依存 DO K=1,20 DO I = 1,5000 DO J = 1,600 if(j.LT.i) then A(I,J) = B(J,I) + us else A(I,J) = 0.0 endif ENDDO ENDDO us = us + 1.2 ENDDO 0.7403731 DO K=1,20 DO i = 1,5000 DO j = 1, min(I-1,600) A(I,J) = B(J,I) + us ENDDO do j=min(I,601),600 A(I,J) = 0.0 enddo ENDDO us = us + 1.2 ENDDO 0.6345739 秒 49 ループ内の条件分岐 (2) 制御の移動 30 50 10 DO K=1,100 DO I = 1,5000 DO J = 1,600 if(B(J,I)-1000.0) 30,50,50 A(I,J) = B(J,I) + us goto 10 A(I,J) = 0.0 continue ENDDO ENDDO us = us + 12.5 ENDDO 3.127317 DO K=1,100 DO I = 1,5000 DO J = 1,600 if(B(J,I).lt.1000.0) then A(I,J) = B(J,I) + us else A(I,J) = 0.0 endif ENDDO ENDDO us = us + 12.5 ENDDO 2.840406 秒 50 I/O 書式の有無、出力方法で差が出る 100 Do k=1,10 DO I = 1,5000 DO J = 1,600 A(I,J) = B(J,I) + us ENDDO ENDDO us = us + 12.5 write(10,100) ((A(i,j),i=1,6000),j=1,500) ENDDO format(20(1PE14.7)) 39.80807 秒 ※READ文も同様 write(10,100) ((A(i,j),j=1,500),i=1,6000) TIME= 39.69929 write(10) ((A(i,j),j=1,500),i=1,6000) TIME= 0.7977569 write(10) ((A(i,j),i=1,6000),j=1,500) TIME= 0.7601511 write(10) A TIME= 0.5792830 homeとworkの使い分け /homeにはプログラムや入力データ(変更しないようなデータ) /workには中間ファイル、結果ファイル(バイナリ、テキスト) 最終的に保存したいデータは自分のマシンで管理 ※実行時は/work1を使用 51 I/O (Lustre) ●パラメータによる高速化 ディレクトリ単位でストライプ設定が可能 > lfs setstripe -c 8 -s 1m ./stripe8 並列数(MPI) 並列数 Stripe_count Stripe_size 時間(秒 時間 秒) 1 1 1048576 14.33704 1 2 1048576 1 2 4194304 1 8 1048576 4.064054 2 8 1048576 1.749849 4 8 1048576 1.030777 9.635327 13.21164 ※fortran,2GBのMPI分散ファイルをwrite インタラクティブ、/work0での値 52 キャッシュチューニング キャッシュチューニングとは キャッシュミス データ・グルーピング 収集/拡散 ブロック化(1次元/2次元/効果) キャッシュコンフリクト プリフェッチ ツール/オプションの活用 53 キャッシュチューニング(イメージ) 高速のL1,L2,L3を活用 メモリ A,Bを同時使用時にBを使うには Aが邪魔(キャッシュミス) A CPU A B キャッシュ レジスタ 高速 A 低速 B A A B B A,Bを分割すれば同時に利用可能 TSUBAMEのCPU => Xeonの3次キャッシュは12MB(3coreで6Mを共有) 54 キャッシュミス DIMENSION A(10,100) A( 1,1) A( 6,1) A( 1,2) A( 2,1) A( 7,1) A( 2,2) A( 3,1) A( 8,1) A( 3,2) A( 4,1) A( 9,1) A( 4,2) A( 5,1) A(10,1) A( 5,2) A(1,3)を使用する場合は メモリからロードが必要 キャッシュミス DO I=1,10 DO J=1,100 A(I,J)= ENDDO ENDDO DO J=1,100 DO I=1,10 A(I,J)= ENDDO ENDDO A(1,1)~A(5,2)までは1度のロードでOK 55 データ・グルーピング A = 0.0d0 DO j=1,N i = LIST(j) A = A + X(i)*Y(i) + Z(i) ENDDO A = 0.0d0 DO j = 1,N i = LIST(j) A = A + R(1,i)*R(2,i) + R(3,i) ENDDO 一つの配列にまとめることにより、 ストライドの短いアクセスで計算ができる。 56 実際の例 real*8 ar(n1*n2+1,n),cr(n1*n2+1,n),trigsr(n) real*8 ai(n1*n2+1,n),ci(n1*n2+1,n),trigsi(n),wi do 10 j=1,n1*n2 : cr(j,lj1)=t1+s5 ci(j,lj1)=t3+s11 cr(j,lj2)=trigsr(lk1)*f1-trigsi(lk1)*f2 ci(j,lj2)=trigsi(lk1)*f1+trigsr(lk1)*f2 cr(j,lj3)=trigsr(lk2)*f3-trigsi(lk2)*f4 ci(j,lj3)=trigsi(lk2)*f3+trigsr(lk2)*f4 : 10 continue データを元の配列に入れる キャッシュの利用向上による高速化と 配列乗せ替による負荷増加のバランス 本件では 47sec -> 32 sec (TSUBAME1での計測事例 での計測事例) での計測事例 real*8 ar(n1*n2+1,n),cr(n1*n2+1,n),trigsr(n) real*8 ai(n1*n2+1,n),ci(n1*n2+1,n),trigsi(n),wi real*8 ccr(6,n1*n2+1),cci(6,n1*n2+1) <-- 追加 do 10 j=1,n1*n2 : ccr(1,j)=t1+s5 cci(1,j)=t3+s11 ccr(2,j)=trigsr(lk1)*f1-trigsi(lk1)*f2 cci(2,j)=trigsi(lk1)*f1+trigsr(lk1)*f2 ccr(3,j)=trigsr(lk2)*f3-trigsi(lk2)*f4 cci(3,j)=trigsi(lk2)*f3+trigsr(lk2)*f4 : 10 continue do 22 ii=1,6 lj=la*(ii-1)+listj(i,p) do 21 j=1,n1*n2 cr(j,lj)=ccr(ii,j) 21 continue 22 continue : 57 収集/拡散 : DO K = 1,1000 CALL SUB (A) ENDDO : : 収集 拡散 SUBROUTINE SUB(A) : DO I = 1,10000,100 A(I) = SQRT( A(I) ) ENDDO : DO I = 1,100 AA(I) = A(I*100-99) ENDDO DO K = 1,1000 CALL SUB (AA) ENDDO DO I = 1,100 A(I*100-99) = AA(I) ENDDO : ストライド100の飛びアクセス SUBROUTINE SUB(AA) : 連続アクセス DO I = 1,100 AA(I) = SQRT( AA(I) ) ENDDO : 収集と拡散によるコストの増加 と アクセスの連続化による高速化 とのバランス 58 収集/拡散(2) (check06) do ii=1,N/100 BB(ii) = B(ii*100-99) enddo DO K=1,100 DO j = 1,M CALL SUB05(A,BB,N,M,US,j) ENDDO US = US + 1.2 ENDDO : SUBROUTINE SUB05(A,B,N,M,US,j) INTEGER N,M REAL*8 A(N,M),B(N),US DO i = 1,N/100 A(I,J) = B(I) + us ENDDO 連続アクセス RETURN END 収集 DO K=1,100 DO j = 1,M CALL SUB05(A,B,N,M,US,j) ENDDO US = US + 1.2 ENDDO : SUBROUTINE SUB05(A,B,N,M,US,j) INTEGER N,M REAL*8 A(N,M),B(N),US DO i = 1,N,100 A(I,J) = B(I) + us ENDDO RETURN ストライド100の飛びアクセス END > ./check06 TIME= 1.783797 FORTRAN STOP 5.9991756E+07 > ./check06a TIME= 0.2309182 FORTRAN STOP 5.9991756E+07 59 ループのブロック化 ループ内の配列使用をブロック化することにより利用効率が向上 (ストリップマイニング) DO K=1,10 DO J = 1,600 DO I = 1,5000 A(I,J) = B(J,I) + US ENDDO ENDDO US = US + 12.5 ENDDO 1.061170 IBLK=100 DO K=1,10 DO J = 1,600 DO II = 1,5000,IBLK DO I = II,MIN(II+IBLK-1,5000) A(I,J) = B(J,I) + US ENDDO ENDDO ENDDO US = US + 12.5 ENDDO 1.0234211 (秒) ※次元の並びが揃っている場合の方が効果が高い 60 ブロック化(2次元) DIMENSION A(6000,500),B(500,6000) DO J = 1,500 DO I = 1,6000 A(I,J) = B(J,I) + C ENDDO ENDDO (sample3) DIMENSION A(6000,500),B(500,6000) IBLK=200 DO JJ = 1,500,IBLK DO II = 1,6000,IBLK DO J = JJ,MIN(JJ+IBLK-1,500) DO I = II,MIN(II+IBLK-1,6000) A(I,J) = B(J,I) + C ENDDO ENDDO ENDDO ENDDO ブロック部分をキャッシュへ J I I J J I J I 61 ブロック化の効果 ブロック化を計測(前頁例) サイズ350が最速(右表) 350*350*8*2 = 1960K (REAL*8で宣言) 実装キャッシュ=12M(L3) (3coreで6Mを共有) ケース ブロック 時間(秒) base -- 1.079675 1 100 0.2782011 2 200 0.2680869 3 300 0.2493160 4 350 0.2130771 5 400 0.2610769 ※条件により変化します 62 キャッシュコンフリクト キャッシュ・コンフリクト(競合ミス) (32の倍数に出やすい) REAL*8 A(4099,1024),B(1025,4096) REAL*8 A(4096,1024),B(1024,4096) TIME= Do k=1,100 DO J = 1,1024 DO I = 1,4096 A(I,J) = B(J,I) + us ENDDO ENDDO us = us + 12.5 ENDDO TIME= 5.161250 5.044347 REAL*8 A(4099,1028),B(1028,4099) TIME= 3.702582 REAL*8 A(4100,1030),B(1030,4100) TIME= 3.656126 ※少し大きめが良さそう 63 キャッシュコンフリクト(イメージ) キャッシュ A(1) 1 A(2) 2 A(3) 3 D(1) 1 D(2) 2 D(3) 3 D(4) 4 D(5) 5 メインメモリ A(10) B(9) B(1) 1 B(2) 2 B(10) C(2) 2 C(3) 3 C(4) 4 C(5) 5 キャッシュライン A(1) A(10) B(9) c(8) D(7) A(2) A(3) B(1) B(10) C(9) D(8) B(2) C(1) C(10) D(9) A(4) 4 A(5) 5 B(3) C(2) D(1) D(10) B(4) C(3) D(2) A(6) 6 A(7) B(5) C(4) D(3) B(6) C(5) D(4) A(8) B(7) C(6) D(5) B(8) A(9) B(8) C(7) D(6) A(1) A(10) B(7) C(4) D(1) A(2) A(3) A(11) B(8) C(5) D(2) A(12) B(9) C(6) D(3) A(4) B(1) B(10) C(7) D(4) A(5) B(2) B(11) C(8) D(5) A(6) B(3) B(12) C(9) D(6) C(1) 1 B(3) 3 B(4) 4 B(5) 5 B(6) 6 B(7) D(6) 6 C(6) 6 C(7) A(1) 1 A(2) 2 D(1) 1 D(2) 2 C(4) 4 C(5) 5 A(3) 3 B(1) 1 D(3) 3 A(4) 4 C(6) 6 D(4) 4 B(2) 2 B(3) 3 A(5) 5 A(6) 6 D(5) 5 D(6) 6 C(1) 1 C(1) 2 B(4) 4 B(5) 5 A(7) B(4) C(1) C(10) D(7) A(8) B(5) C(2) C(11) D(8) C(1) 3 B(6) 6 A(9) B(6) C(3) C(12) D(9) ※ A(i) , B(i) , C(i) , D(i) を使用 D(10) 64 プリフェッチ プリフェッチ キャッシュメモリ上にデータを先読みする機能。 記述例 cmem$ cmem$ cmem$ cmem$ do j=1,n prefetch a(1),b(1) prefetch a(5),b(5) prefetch a(9),b(9) do k=1,n,4 prefetch a(k+12),b(k+12) c(i,j) = c(i,j) + a(k) * c(i,j) = c(i,j) + a(k+1) c(i,j) = c(i,j) + a(k+2) c(i,j) = c(i,j) + a(k+3) enddo < 難易度は高い > このあたりをアクセス しているときに メインメモリ b(k) * b(k+1) * b(k+2) * b(k+3) ここをアクセスしている間に メモリからキャッシュへ転送 このあたりをキャッシュへ ロードするように要求する 65 ツールの使用 (sample,cpp) キャッシュチューニングはトライアンドエラー cppの活用 REAL*8 A(5000,600),US REAL*8 B(600,5000) Do k=1,10 DO I = 1,5000 DO J = 1,600 A(I,J) = B(J,I) + us ENDDO ENDDO us = us + 12.5 ENDDO #define A(n1,n2) A(n2,n1) #define B(n1,n2) B(n2,n1) 配列の宣言によりストライドが 大きく変化して性能を左右する cpp –include cpp.inc sample.F sample1.f コンパイル 66 コンパイルオプションの活用 (check01) コンパイルオプションを活用したデバック 1: real*8 a(100) 2: do i=1,100 プログラムをチェック 3: a(i)=0.0 -Mstandard 4: enddo 5: i=17400 6: a(i) = 1.5 コンパイルリスト 7: stop -Mlist 8: end $ pgf95 check01.f > ./a.out Error: segmentation violation, address not mapped to object to object : ※役に立つオプションが多数あり $ pgf95 -C check01.f (マニュアルを参照) $ ./a.out マニュアルを参照) 0: Subscript out of range for array a (check01.f: 7) subscript=17400, lower bound=1, upper bound=100, dimension=1 ※ intelコンパイラでは コンパイラでは -CB 67 修正 cuda対応コンパイラの利用 プログラムの準備 !$acc region DO k=1,100 DO J = 1,600 DO I = 1,5000 c(j,i) = c(j,i) + A(I,J) * B(J,I) ENDDO ENDDO ENDDO !$acc end region (cuda) OpenACC利用の場合は 利用の場合は http://tsubame.gsic.titech.ac.jp/node/730 指示行の挿入 GPUに処理させたい部分を に処理させたい部分を 挟んで指示行を記述します コンパイル/実行 最新版コンパイラに切り替え > . /usr/apps.sp3/isv/pgi/pgiset16.9.sh > pgf95 -Minfo=all -fastsse -o k20x cuda/cuda.f wclock.c > ./k20x TIME= 2.313200 29876802500000.00 TSUBAME2.5になって になって > pgf95 -Minfo=all -fastsse -o k20x -ta=nvidia cuda/cuda.f wclock.c 処理が高速になりました > ./k20x TIME= 0.4608109 29876802500000.00 このオプションが大切 バッチ t2sub –q G -W group_list=t2g-xxx -l select=1:gpus=1 ./tesla.sh (バッチ) バッチキューはG/S/H バッチキューはG/S/Hを使用 G/S/Hを使用 68 リスタート計算 (sample20) ●時計経過を監視してリスタートファイルを出力 t0(2)=second() now = t0(2) - t0(1) if (now.ge.limit) then リスタートが数回ある場合は必要 rewind 10 write(10) N,M,IBLK,ITAR,IRESOUT,IRESIN,ADD,Limit,K write(10) A,B,US write(6,*) ' TIME= ',now,' resart out ' iresout=1 : ●リスタートファイルから入力 iresin=0 上限値 read(5,*) iresin,limitin if (limitin.eq.0) limit=100 if (iresin.gt.0) then read(10) N,M,IBLK,ITAR,IRESOUT,IRESIN,ADD,Limit,ist read(10) A,B,US ist=ist+1 go to 50 計算を再開する添え字 : ※なるべく粒度の大きなループ(メインループ等 なるべく粒度の大きなループ メインループ等)に使用 メインループ等 に使用 リスタート無 > ./sample20 < in_dat TIME= 2.622344 FORTRAN STOP --< --< 0 0 >->-719400028.4671783 計算済みの添え字 コントロール情報 計算情報 リスタート有 > ./sample20 < in_dat TIME= 1.006760 FORTRAN STOP > vi in_dat --< --< 0 1 >->-restart out > ./sample20 < in_dat --< --< 1 1 >->-TIME= 1.006066 restart out FORTRAN STOP > ./sample20 < in_dat --< --< 1 1 >->-TIME= 0.6484759 719400028.4671783 FORTRAN STOP 69 巨大配列のプログラム (check2G) ●次のようなエラーが出る場合は使用している配列が大きいためです。 > pgf95 -o 2G check2G.f wclock.c (~ pgi 14.10) /usr/apps.sp3/isv/pgi/14.6/linux86-64/14.6/lib/libpgf90.a(initpar.o): In function `__hpf_myprocnum': initpar.c:(.text+0x6): relocation truncated to fit: R_X86_64_PC32 against symbol `__hpf_lcpu' defined in COMMON section in /usr/apps.sp3/isv/pgi/14.6/linux86-64/14.6/lib/libpgf90.a(initpar.o) : > pgf95 -o 2G check2G.f wclock.c (pgi 15.1 ~) check2G.o: In function `MAIN': /home0/usr2/xxxxxx/tune/./check2G.f:12:(.text+0x7d): relocation truncated to fit: R_X86_64_32S against `.bss' ●対応としては、次のオプションを付加してください。 > pgf95 -fastsse -o 2G -mcmodel=medium check2G.f wclock.c > ./2G TIME= 9.352160 30301154000000.00 Warning: ieee_inexact is signaling FORTRAN STOP ●ワーニングが気になる場合、 > export NO_STOP_MESSAGE=yes > ./2G TIME= 9.364583 30301154000000.00 ●GPUを使う場合、配列サイズを削減してください。 -mcmodelが必要なプログラムは mcmodelが必要なプログラムはGPU が必要なプログラムはGPUで動きません。 GPUで動きません。(pgi で動きません。(pgiコンパイラの利用制限 (pgiコンパイラの利用制限) コンパイラの利用制限) 70 並列プログラムによる高速化 並列化概要 ・・・・・・・・・・・・・・・・・・・・・・・・ 並列化とは SMP並列プログラム ・・・・・・・・・・・ 共有メモリ型並列手法 MPI並列プログラム ・・・・・・・・・・・・・ 分散並列化手法 性能評価 ・・・・・・・・・・・・・・・・ コストの把握と計測 71 並列プログラミング 並列化とは 並列処理による実行時間の短縮 並列化に特有な用語 並列化の分類 並列の種類と特徴 移植 並列性能 72 並列化とは 1つの処理を、幾つかの小さな処理に分割し、複数の タスク(CPU)で実行すること シングル実行 処理A CPU0 並列実行 処理A1 処理A2 処理A3 処理A4 CPU0 CPU1 CPU2 CPU3 73 並列処理による実行時間の短縮 並列処理を行った場合の実行時間の短縮 →CPU時間ではなく、経過時間 経過時間が短縮される 経過時間 経過時間 CPU0 CPU時間(0) CPU時間(1) CPU時間(2) CPU時間(3) CPU0 CPU時間(0) CPU1 CPU時間(1) CPU2 CPU時間(2) CPU3 CPU時間(3) 1CPUで実行した場合の経過時間 オーバヘッド (処理を各タスクで並列 実行させるための処理) 4CPUで実行した場合の CPU時間の合計 4CPU(並列)で実行した場合の経過時間 74 並列化(に特有な用語) 並列処理のオーバーへッド 並列処理を行なう際に複数のタスクを生成する。この時間が実行時間に加わる。ま た、同期処理、待ち合わせなども加算される。これらの増加時間をオーバーヘッドと いう 粒度の大きさ 粒度とは、一般的には実行一回あたりに要する時間をいう。粒度が小さいと並列化 の効果がオーバーへッドに隠れてしまい、性能をあげることができない。 負荷分散 各プロセッサに割り当てられる負荷が不均一だと、タスクの終了時に待ちが生じて しまい、性能が上がらない場合がある。ループの分割方法や処理の分割方法を検 討する必要がある。 並列化率 シングルプロセッサで実行した際の実行時間中に占める、並列実行される部分の 実行時間の比率をいう。 プロセスとタスク MPI実行時に、同時に実行されるプログラムをプロセスとよび、SMP並列で実行時 に生成されるスレッドをタスクという(ただし環境により言葉の意味が変化) 75 並列化の分類 処理の高速化 処理の並列化 処理手順の検討 高速化チューニング OpenMP Pthred SMP並列 並列 スレッド ハイブリッド並列 MPI プロセス並列 MPI Linda,DDI 76 並列の種類と特徴 MPI --- 複数のノードにわたってプログラムを実行 ノード間でデータをやり取りしますので、並列部分をどの様に 処理してやるのか明確にプログラム(難易度:難) OpenMP -- 1ノード(12Core)内でメモリ共有による並列化を 指示行を入れることにより実現 並列可能部分に指示行を入れて明示的にコンパイラに指示 (難易度 : 中間) 自動並列 - コンパイラがプログラムを解析して並列化 コンパイルオプションで -Mconcur を指定 (難易度 : 易) ※TSUBAMEの のCPUはハイパースレットが はハイパースレットがONになっているため、 になっているため、 はハイパースレットが 24(S,Hキュー キュー),16(Vキュー キュー),64(L系 系)まで指定可能です。 まで指定可能です。 キュー キュー 77 並列プログラムの移植 マシンによって若干の違いがあるので、プログラムを みながら並列化を実施 元のマシンでどのような並列をしていたか判る場合 自動並列 -Mconcur を付加 (pgi例) openMP -mp を付加 (pgi例) MPI mpif90コマンドでコンパイル 不明の場合 プログラム内に !$OMP という記述がある openMP プログラム内でMPI_Initを呼んでいる MPI !$OMPなどが無い場合 自動並列 !cdir !pdir などの記述がある場合はその部分が並列化のポイントになる可能 性が高い。指示行の意味が分かる場合はopenMPの指示行に置き換える 上記が組み合わせて使用されている場合もある 計算結果の検証は必ず実施する 78 並列性能 性能の指標(加速率) 性能の指標(並列化率) 30 理想 標準 スーパー 25 加速率 CPU数に応じた処理時間の短縮 理想形 : 1CPU時=NCPU時 *N 標準形 : 理想形の ~80%程度 スーパースケール : キャッシュ効果により台数効果 以上の性能 20 15 10 5 並列効果= 1 / (( 1 - p) + p / Np) p : 並列化率(0~1.0) Np : プロセッサ数 0 0 10 20 30 CPU数 並列性能(加速率) 79 SMP並列プログラミング SMP並列とは 自動並列 並列化阻害要因 自動並列化の条件 OpenMP並列 OpenMPの指示行 指示行と環境変数 例題 並列化特有の誤差 80 SMP並列とは Symmetrical Multi Processor (対称型マルチプロセッサ)上での 並列処理 メモリーを共有する 自動並列 コンパイラがプログラムを解析して並列化 OpenMP 並列実行可能部分に指示行を挿入 スレッドプログラミング 並列実行可能部分をスレッドタスクにして実行する ※自動並列で性能が出ない場合はOpenMPを使用 FAQ:利用可能なノード内の並列数を教えてください。 利用可能なノード内の並列数を教えてください。 http://tsubame.gsic.titech.ac.jp/node/323 なども参照ください 81 メモリ共有とは 分割した各タスクでデータを共有 Imax Imax/nproc J タスク1 タスク2 タスク3 タスク4 ※共有のためタスク間で相互干渉(上書 共有のためタスク間で相互干渉 上書)の可能性あり 上書 の可能性あり DO i=1,n-1 A(i) = A(i) + A(i+1)*C ENDDO 82 自動並列とは コンパイラが、並列実行可能な記述を抽出 → 複数のタスクに割り当てて実行する do j = 1,100 do i = 1,1000 A(i,j) = B(i,j) + C(i,j) enddo enddo CPU0 do j = 1,25 do i = 1,1000 A(i,j) = B(i,j) + C(i,j) enddo enddo CPU1 do j = 26,50 do i = 1,1000 A(i,j) = B(i,j) + C(i,j) enddo enddo CPU2 do j = 51,75 do i = 1,1000 A(i,j) = B(i,j) + C(i,j) enddo enddo CPU3 do j = 76,100 do i = 1,1000 A(i,j) = B(i,j) + C(i,j) enddo enddo 83 自動並列化 (sample2) コンパイルオプションを指定して並列化 do 10 j=1,n1*n2 li1=ia+listi(i,p) li2=ib+listi(i,p) lk1=listk(i,p)+1 : 10 continue 並列オプション メッセージの出力 pgf95 –Mconcur –Minfo=all sample2.f ifort -parallel -par-report2 sample2.f 並列化のメッセージ PGI : 8, Parallel code generated with block distribution INTEL : (col. 10) remark: LOOP WAS AUTO-PARALLELIZED. メッセージを確認 (メッセージが無い場合 メッセージが無い場合) メッセージが無い場合 指示行による並列 84 並列化阻害要因 サブルーチンコール 間接メモリ参照 ループ内のデータの依存関係 ループから飛び出し 曖昧なポインタ I/O ※高速化阻害要因と内容的にはほぼ同じもの 85 自動並列化対象(Fortran) 対象となるループ DOループ 対象となるデータ の型 すべての整数型・論理型、単精度・倍精度の 実数型・複素数型 (文字型は対象外) 対象となるループ 代入文、IF文、GOTO文、CONTINUE文、 中に許される文 CASE構文 (call文、入出力文は対象外) 対象となる演算 加減乗除算、論理演算、関係演算、型変換、 組込み関数 86 自動並列化の条件 ループ構造 – ループからの途中飛び出し、飛び込みがない。 – ループの繰り返し回数がループの入り口で決まっている ループ内データ依存関係が以下の条件のどれかを満たす。 – 配列要素の定義、参照がループの繰り返しに閉じている。 – 配列要素が参照のみである。 – スカラー変数がインデックス変数(i=i+kの形)である。 (ただしif文の下で定義されていないこと) – スカラー変数で、ループ内の出現が定義後参照である。 ループ内データ依存関係が不明な場合 – 並列化指示行を指定することにより、並列化可能となる。 87 並列化されない例 ループからの飛出しがある DO j = 1,N DO i = 1,N A(i,j) = sqrt( B(i,j) ) if (A(i,j) .ge. LIM ) go to 100 ENDDO ENDDO 100 continue 配列要素の定義参照がループ内に閉じていない DO k=1, N A(k) = B(k+1) B(k) = C(k) ENDDO 88 並列化条件の例 スカラー変数の定義と引用が閉じていない DO i = 1, N C(i) = t t = B(i) ENDDO → 並列化できない スカラー変数ループを出た後で参照されている DO i = 1, N t = B(i) C(i) = t ENDDO s = t →並列化できる 並列化できる 89 openMPとは プログラマーが並列実行可能な記述部分に指示行を挿入 プログラマーが並列実行可能な記述部分に指示行を挿入 → OpenMP並列化 並列化(複数のタスクに割り当てて実行 並列化 複数のタスクに割り当てて実行) 複数のタスクに割り当てて実行 !$omp parallel do do j = 1,100 do i = 1,1000 A(i,j) = A(i,j) + B(i,j) enddo enddo CPU0 do j = 1,25 do i = 1,1000 A(i,j) =A(i,j) + B(i,j) enddo enddo CPU1 do j = 26,50 do i = 1,1000 A(i,j) =A(i,j) +B(i,j) enddo enddo 並列化できないループも OpenMP並列化指示行で 並列化してしまう。 (結果不正の発生に注意 結果不正の発生に注意) 結果不正の発生に注意 CPU2 do j = 51,75 do i = 1,1000 A(i,j) =A(i,j) +B(i,j) enddo enddo CPU3 do j = 76,100 do i = 1,1000 A(i,j) = A(i,j) +B(i,j) enddo enddo 90 openMPの指示行 並列を指定する記述(必須 並列を指定する記述 必須) 必須 (sample2b) ループの分割方法を指定 !$OMP PARALLEL DO SCHEDULE(STATIC,n1),DEFAULT(SHARED) !$OMP+PRIVATE(j,li1,li2,lk1,lj1,lj2,s1,s2) 無指定の変数は共有扱い do 10 j=1,n1*n2 li1=ia+listi(i,p) DEFAULT(FIRSTPRIVATE) とすると、 継続行 最初にコピーした値をスレッド別に持つ : スレッド別に値を持つ 10 continue !$OMP END PARALLEL DO fortran for (k=0;k<200;k++) { #pragma omp parallel for private(i,j) for (j =0;j<m;j++){ for (i =0;i<n;i++){ a[j][i] = b[j][i] + us C ; コンパイルオプションに –mp (pgi) / -openmp (intel) / -fopenmp (gnu) を指定 91 openMPの指示行と環境変数 指示文 SINGLE PARALLEL DO MASTER CRITICAL BARRIER シングルでの実行(待ち合わせあり) 並列で繰り返し マスタースレッドで実行 1度に1スレッドずつ 待ち合わせ 環境変数 OMP_SCHEDULE OMP_NUM_THREADS OMP_NESTED (pgiでは未対応 では未対応) では未対応 DOの繰り返し方法の指定 スレッド数 並列性のネスト(FALSE) 92 openMPの例 (openmp) C #include <stdio.h> main() { #pragma omp parallel { printf("hello world from %d of %d¥n", omp_get_thread_num(),omp_get_num_threads()); } } pgcc -mp openmp.c > ./a.out hello world from 0 of 1 > export OMP_NUM_THREADS=2 > ./a.out hello world from 0 of 2 hello world from 1 of 2 FORTRAN !$ use omp_lib !$omp parallel write(*,100) "hello world from",omp_get_thread_num(), * " of",omp_get_num_threads() !$omp end parallel 100 format(a,i2,a,i2) stop end > pgf95 –mp openmp.f > export OMP_NUM_THREADS=2 > ./a.out hello world from 0 of 2 hello world from 1 of 2 FORTRAN STOP 93 並列不可ループの並列化 REAL*8 B(100000),us,sum N=100000 us = 0.0 do i=1,N b(i) = 1.0D0 enddo !$omp parallel do Do k=1,20 DO i = 1,N-1 B(I) = B(I) + B(I+1) + us ENDDO us = us + 1.2D0 ENDDO sum=0.0 do i=1,N sum=sum+B(i) enddo write(6,*) sum stop end > pgf95 -mp –Minfo=all -o check04 check04.f > ./check04 230662389617.9999 > export OMP_NUM_THREADS=2 > ./check04 191145160058.4789 !$omp !$omp > > > (check04) REAL*8 B(100000),us,sum,C(100000) N=100000 us = 0.0 do i=1,N B(i) = 1.0D0 C(i) = 0.0D0 enddo Do k=1,20 parallel do DO i = 1,N-1 C(I) = B(I+1) ENDDO parallel do DO i = 1,N-1 B(I) = B(I) + C(I) + us ENDDO us = us + 1.2D0 ENDDO : pgf95 -mp -Minfo=all -o check04a check04a.f export OMP_NUM_THREADS=2 ./check04a 230662389617.9999 94 並列特有の誤差 real a a=0.0 !$omp parallel do reduction(+:a) do i=1,30 do j=1,100000 a=a+0.000001 enddo enddo write(6,*) a stop end (check02a) どのコンパイラも同じです > ifort -openmp -r8 -o check02 check02.f -openmp-report2 check02.f(4): (col. 6) remark: OpenMP DEFINED LOOP WAS PARALLELIZED. > export OMP_NUM_THREADS=1 > ./check02 3.00000000006543 集計順位が並列数に左右されて > export OMP_NUM_THREADS=2 > ./check02 2.99999999993357 > export OMP_NUM_THREADS=4 > ./check02 3.00000000000292 同じ値にならない場合があります ・集計数の 集計数の多いプログラム ・モンテカルロ法 ・モンテカルロ法 などは注意してください 95 MPI並列プログラミング MPI並列の特徴 MPIプログラムの構造 主要関数/1:1通信 練習 多重通信/通信コスト ロードインバランス 通信の削減 分散ファイル MPI通信の動作(Voltaire) 異常終了時の対策 96 MPI並列の特徴 Message Passing Interface の略 分散メモリ型の並列計算機で2つ(以上)のプロセス間でのデータを やりとりするために用いるメッセージ通信操作の仕様標準 実装方法により、多少の差が出る - openmpi , mvapich2 ---- IB(InfiniBand)を使用 , Vキューでは不可 - mpich2 --- Eth を使用 → V/U キュー(仮想マシン) ,他でも可 MPIプログラムはおおむね以下の手順で作成する – – – – MPIの関数定義インクルードファイルを記述。 MPIの初期化に関する記述を行なう。 計算を並列化 通信 どこを並列化するのかを充分に吟味する 97 並列部分の検討 計算の依存関係の有無を吟味 気象(領域を分割) 中性子挙動計算 -- モンテカルロ -(ヒストリー単位) 98 MPI並列プログラムの構造 ○分割計算の結果をrank=0にて集計するためのMPIプログラムの例 include 'mpif.h' integer ierr,np,npe call mpi_init(ierr) ← 初期処理(決まり文句) call mpi_comm_rank(mpi_comm_world,np,ierr) ← rankの取得(決まり文句) call mpi_comm_size(mpi_comm_world,npe,ierr) ← PE数の取得(決まり文句) : if (np.eq.0) then do n=1,npe-1 call mpi_recv(m(0,n),2,mpi_integer,n,n, ←受信(親=rank0) & mpi_comm_world,ist,ierr) (データを受け取る回数分必要) enddo else call mpi_send(m(0,np),2,mpi_integer,0,np, ←結果の送信 & mpi_comm_world,ierr) (rank=0以外は1回送信のみ) endif call mpi_barrier(mpi_comm_world,ierr) ← 同期 : ※このような処理では本来はREDUCE このような処理では本来はREDUCEや REDUCEやGATHERを使用 GATHERを使用 処理 : call mpi_finalize(ierr) ← 終了処理(決まり文句) : 99 end MPIの主要関数 'mpif.h' USE MPI MPI_Init MPI_Comm_rank MPI_Comm_size MPI_Recv MPI_Send MPI_Irecv MPI_Isend MPI_Sendrecv MPI_Barrier MPI_Wait MPI_Gather MPI_Scatter MPI_Allgather MPI_Alltoall MPI_Reduce MPI_Allreduce MPI_Bcast MPI_Finalize インクルードファイル 同 f90用モジュール 初期処理 自分のプロセス番号 全体のプロセス数 受信処理(1:1) 送信処理(1:1) ブロック受信処理(1:1) ブロック送信処理(1:1) データの交換やシフト(1:1) 全プロセスがこの手続きを呼び出すまで待ち合わせる 処理完了の待ち合わせ 送信バッファの内容をルートプロセスへ送信(n:1) ルートがプロセスへ送信(1:n) MPI_GATHER + MPI_SCATTER 全プロセスでデータ交換(n:n) 全プロセス上の値を1つの値にリダクション リダクション演算と全プロセスへの結果の展開 ルートから、全てのプロセスへメッセージを送信 MPI機能の使用終了指示 最初の5 文字までが大文字(fortranでは区別無し) ※関数名の最初の 最初の 5文字までが大文字 100 通信 A(Imax,Jmax)というデータ配列をプロセスに分割 DO J=1,Jmax DO I= ,Imax/nproc A(I,J)= A(I,J) + A(I+1,J)* ・・・ ENDDO ENDDO Imax/nproc Imax/nproc 通信 J の計算には 通信 A A が必要 Imax/nproc A (各プロセスにデータを分割して計算) 101 1:1通信(シフト) integer sendbuf,recvbuf integer myrank,groupsize,dest,source,error integer status (MPI_STATUS_SIZE) integer comm comm = MPI_COMM_WORLD call MPI_COMM_RANK(comm, myrank, error) call MPI_COMM_SIZE(comm, groupsize, error) dest= mod( myrank+ 1, groupsize) source= mod( groupsize+ myrank- 1, groupsize) call MPI_SENDRECV( sendbuf, 1, MPI_INTEGER, dest, 0, * recvbuf, 1, MPI_INTEGER,source, 0, * comm, status, error) Imax/nproc ※ Imax/nproc 通信 J send,recvで 記述する方法 もあります 領域 通信 領域 逆向きで Imax/nproc 領域 ※ を通信 102 1:1通信(交換) Imax/nproc Imax/nproc 領域 領域 include "mpif.h" INTEGER irecv(3,LL),isend(3,LL),isize dimension ist(mpi_status_size) integer np,npe,ier ! ! ! ! call mpi_comm_rank(mpi_comm_world,np,ierr) ileng = isize*3 if (np.eq.0) then /* isendの値を設定する */ call mpi_sendrecv(isend(1,1),ileng,mpi_integer,1,12, * irecv(1,1),ileng,mpi_integer,1,67, * mpi_comm_world,ist,ierr) /* irecvに受け取った値を利用する */ elseif(np.eq.1) then /* isendの値を設定する */ call mpi_sendrecv(isend(1,1),ileng,mpi_integer,0,67, * irecv(1,1),ileng,mpi_integer,0,12, * mpi_comm_world,ist,ierr) /* irecvに受け取った値を利用する */ endif データを交換 103 練習 (sample5) MPI(1:1)通信プログラムの作成 implicit none include 'mpif.h' character*8 abc integer irank, isize,ist(2),ierr call MPI_init(ierr) call MPI_comm_size(MPI_COMM_WORLD, isize, ierr) call MPI_comm_rank(MPI_COMM_WORLD, irank, ierr) > source set_ompi-1.6.5_p16.1.sh (> source /usr/apps.sp3/isv/pgi/pgiset16.9.sh) > mpif90 -o mpitest sample5.f > mpirun -np 2 mpitest RANK= 1 ,DATA=TSUBAME RANK= 0 ,DATA=TSUBchac このプログラムは2並列で動きます 並列数を問わないようにしたい時 → sample5a.f を参照 if (irank.eq.1) then abc= 'TSUBAME' call MPI_send(abc,4,mpi_character,0,0,MPI_COMM_WORLD,ierr) else abc='titechac' call MPI_recv(abc,4,mpi_character,1,0,MPI_COMM_WORLD,ist,ierr) endif write(6,*) 'RANK=',irank,' ,DATA=',abc call MPI_finalize(ierr) stop end 104 多重(n:n)通信 do i=1,nxp*nya*nzap sbuff(i) =dar1(indx2a_yztoxz(i)) enddo !-------------------------------------------! call mpi_alltoall(sbuff,nxp*nyap*nzap * ,mpi_double_precision * ,rbuff,nxp*nyap*nzap * ,mpi_double_precision * ,new_world(1),ierr) !-------------------------------------------! do i=1,nx*nyap*nzap dar1(indx1a_yztoxz(i)) =rbuff(i) enddo 1 2 3 0 ※全てのデータを使って処理を行う必要がある場合に使用 105 通信コスト (MPI_Alltoall) データのサイズを固定した場合の処理時間 iproc 2 4 8 16 32 64 128 byte 16 16 16 16 16 16 16 time(sec) 0.0000350475 0.0000429153 0.0005359650 0.0562489033 0.0844089985 0.0955040455 0.1283059120 byte 8000000 8000000 8000000 8000000 8000000 8000000 8000000 time(sec) 0.0073540211 0.0272459984 0.0826160908 0.3879010677 0.9010698795 2.1064548492 4.4459710121 データサイズがCPU数に反比例する場合 iproc 2 4 8 16 32 64 128 byte 512000000 256000000 128000000 64000000 32000000 16000000 8000000 time(sec) 0.4167659283 0.7350251675 1.3801920414 1.8404440880 2.6457960606 3.2575979233 4.4459710121 ※TSUBAME2での実測値です での実測値です 106 集計(1:n通信) t02=t01 - t00 tunetm(21,ipnum+1) = tunetm(21,ipnum+1) + t02 : call MPI_REDUCE(tunetm,rbuf,40*35,MPI_DOUBLE_PRECISION,MPI_SUM, * 0,mpi_comm_world,ierr) CALL MPI_BARRIER(MPI_COMM_WORLD,IERR) CALL MPI_FINALIZE(IERR) if(ipnum.eq.0 ) then do i=1,30 2 1 rbuf(i,33)= 9999999.0 rbuf(i,34)= 0.0 do j=1,nprocs : 集計結果を各プロセスに配信したい 場合はMPI_ALLREDUCE を使用 3 0 107 ロードインバランス 例 : 三角ループの並列化 例 1 I 均等分割 領域4が重い 1 例 2 合計すると ほぼ同じにできる J 通信が複雑 1 2 2 3 3 4 4 4 3 2 1 例 3 分割サイズを可変 1 2 3 4 ※CPUが空転しないように負荷をなるべく均等に 108 通信の削減 格子の分割によって通信量が変化 効率のよい通信方法を選択する 200 400 全部の方向に通信あり 50 200 1/16 100 25 分割数 格子 通信面 1 2 4 8 16 200x400 200x200 200x100 200x50 200x25 -400 400 400 400 2 4 8 16 200x200 200x100 100x100 100x50 400 400 400 300 同じ格子数でも通信面を減らせる(場所により変化) 109 通信の隠蔽 通信の傍らで、計算を行う 通信は並列処理できないので、性能に悪影響 通信回数を少なくして、1回あたりの通信量を増加させる call mpi_isend(m,20,mpi_integer,n,np, & mpi_comm_world,ierr) ←送信 call mpi_irecv(mr,20,mpi_integer,n,n, ←受信 & mpi_comm_world,ist,ierr) call syori ← 通信に関係ない処理を行う call mpi_barrier(mpi_comm_world,ierr) : ← 同期 ※処理量(バリアまでの)が大きいほど隠れやすい 110 入出力の分散ファイル化 入出力データの肥大化 並列計算では領域を分割する --> 巨大データの読み込み、プロセスに配分 --> 時間がかかる 事前に領域別にデータを作成 --> 自分のプロセスだけ入出力すれば良い 計算条件等のファイルは共通で入力または、一斉送信により配分 結果の出力 各プロセスで出力 上書き ファイルにプロセス番号をつけて区分 write(crank,' (i4.4) ') irank < -- character*4 fname='out'//crank < - - character*12 open(10,file=fname,status='new',form='unformatted') write(10) a ※ /workを使用した /workを使用したI/O を使用したI/Oの高速化 I/Oの高速化 並列数 1:1 4:1 8:1 16:1 16:2 32:2 32:4 64:2 n 8000000 2000000 1000000 500000 500000 250000 250000 125000 m 256 256 256 256 256 256 256 256 /work ---58.78780s 27.24773s 12.64137s 3.92834s 4.42065s 4.25156s 3.91888s 3.20444s real*8 a(n,m)の配列を write(10) a として出力 111 ファイル分散化の注意点 ノード数は少ないほうが良い 連続的な出力はさける(連続出力はマシンを高騰させます) 形式は不問 /workを使用する 図は並列数とファイルサーバーの 負荷状況をグラフ化したもの。 同じ並列数でも使用するノード数 によってサーバーへの負荷が変化 ※同じ並列数の場合、ノード数が 少ないほうがファイルサーバー への負荷が少ない 112 MPI-I/O (sample22) ●通信と同じ操作でファイルを出(入)力できる integer irank, isize,ierr,buffsize integer (kind=MPI_OFFSET_KIND) disp INTEGER ISTATUS(MPI_STATUS_SIZE) real*8 A(10000) 出力するデータ数(プロセスあたり 出力するデータ数 プロセスあたり) プロセスあたり : buffsize= 10 作成するファイル disp = irank * buffsize *8 call MPI_File_open(mpi_comm_world,‘MPI-IO.dat', & MPI_MODE_WRONLY+MPI_MODE_CREATE, ファイルのアクセス & MPI_INFO_NULL,IHL,IERR) call MPI_FILE_SET_VIEW(IHL, disp, MPI_REAL8, & MPI_REAL8, 'native', MPI_INFO_NULL, ierr) CALL MPI_FILE_WRITE( & IHL,A,buffsize,MPI_REAL8,ISTATUS,IERR) CALL MPI_File_close(IHL,IERR) 制限:2GB( 制限:2GB(仕様 :2GB(仕様) 仕様) 読み込み時は 下記を入れ替える …,MPI_MODE_RDONLY, … : : CALL MPI_FILE_READ( ●ファイルの生成イメージ buffsize*8 rank=0 disp(rank0) buffsize*8 buffsize*8 rank=1 rank=2 disp(rank1) disp(rank2) buffsize*8 rank=3 MPI-IO.dat disp(rank3) 113 MPI通信の動作イメージ(Mellanox) ノード内はメモリを使用 プロセスあたり150MB~500MB程度をバッファとして使用 --> 実行サイズ+バッファが実行必要サイズになる ノード間通信はシステムメモリも使用 各プロセスのメモリとは別に通信バッファを確保 ノード内通信 ノード間通信 ./sample ./sample ./sample ./sample ./sample 通信 ノード間通信用バッファ ./sample ./sample ./sample ./sample ./sample 114 異常終了時の対策 配列外参照(Segmentation fault , Segmentation Violation , Bus error) --> プログラムを再確認 メモリ不足 0: ALLOCATE: 1920000000 bytes requested; not enough memory forrtl: severe (41): insufficient virtual memory =>> PBS: job killed: mem 32050216kb exceeded limit 31457280kb --> mem 指定値を増加 ( :mem=3gb --> --> :mem=4gb など) 通信エラー http://tsubame.gsic.titech.ac.jp/ja/node/148 等を参照 使用するMPIを吟味(openmpi , mvapitch2 , mpich2) 切り替えて利用が可能(ただし、環境に合ったコンパイルが必要) ※環境によりメッセージが変化します。 利用の手引 6.7 エラーメッセージを参照ください http://tsubame.gsic.titech.ac.jp/docs/guides/tsubame2/html/programming.html#id22 115 バッチジョブの結果リストが無いのですが。? • 以下の手順で確認してください t2stat 実行中 計算の途中経過を知る方法はありますか。? 計算の途中経過を知る方法はありますか。 http://tsubame.gsic.titech.ac.jp/node/177 Yes (実行中) 実行中) No t2quota Disk容量は Disk容量は? 容量は? 予定時間内 No OK(制限以下 OK(制限以下) 制限以下) Yes いま少しお待ちください (終了予定を過ぎている) 終了予定を過ぎている) Limit ファイルを整理してください 容量不足でファイルを カレントに戻せない (課金されます) 課金されます) 出力が大きすぎる (10GB超のリスト) (課金されます) 課金されます) t2stat –n1 の実行マシン情報から http://mon.g.gsic.titech.ac.jp/summary/ により負荷状況を確認 Write文を削減 Write文を削減 PBSの PBSの “-o”を使用 o”を使用 高負荷により ジョブが終了できない (課金されません) 課金されません) soudanにjob番号を連絡 他のジョブに影響しますので必ず連絡してください 資源指定/プログラムを再確認 資源指定 プログラムを再確認 相談してください 116 共通 性能評価 コストの解釈 計測と最適化の注意点 性能解析 プロファイラ(PGI/GNU/並列) 時間関数の組み込み 時間計測/計測コマンド チューニング事例(シングル/並列) 性能表の作成(並列) チューニング実習 並列化(SMP/MPI)実習 117 コストの解釈 負荷部分を特定して重コスト部分の比率を低くする 次の様な負荷分布を持ったアプリケーションを仮定する。 関数 main bas bar baz 負荷比率 8% 14% 33% 45% 関数baz 関数bazのパフォーマンスを bazのパフォーマンスを20% のパフォーマンスを20%改善した 20%改善した 関数bas 関数basを同程度 basを同程度(20%) を同程度(20%) --> --> 全体の9% 全体の9% --> 2.8% --> ※改善の効果が最も大きい箇所に対して集中的に行う 118 計測と最適化および並列化の注意点 開発時間と実行時間の比率 数回しか実行されない特殊用途のプログラム --> チューニング時間が開発時間と処理時間を上回る ? (この場合チューニングする意味はあまりない) この場合チューニングする意味はあまりない) 結果の変化 <<重要>> 最適化する場合の最後の注意点は、機能的に等価であること。 (結果が変化しない) 最適化が、ビット単位での正確な結果を保証するわけではない。 (累積誤差との違いは慎重に見極める) 代表的な入力情報を与えて、プログラムの実行結果を注意深く確認 する必要がある。 119 性能解析 プローブベース アプリケーションコード中に計測コードを埋め込む タイムスタンプや、後で行う解析に役立つ情報を出力する。 プロファイラベース プロファイラベースの解析方法では、プロファイルオプションを 付けてアプリケーションをコンパイルする。 プロファイル付きコンパイルにより、プログラム実行時にパフォー マンスに関する情報を収集する パフォーマンス情報ファイルを生成する。この情報をプロファイラ という外部プログラムが情報を解析する。 120 修正 プロファイラ (sample10) 使用例 $ ssh -Y -C loginlogin-t2.g.gsic.titech.ac.jp –l login_name $ source /usr /usr/apps.sp3/ usr/apps.sp3/isv /apps.sp3/isv/ isv/pgi/pgiset15.10.sh pgi/pgiset15.10.sh $ pgf95 -Mprof= Mprof=func -o sample10 sample10.f wclock.c (func or lines) lines) (圧縮オプションを付加して接続: 圧縮オプションを付加して接続:推奨) 推奨) (バージョンを指定) バージョンを指定) (コンパイル) コンパイル) $ ./sample10 (実行 (実行) 実行) $ ls pgprof.out (ファイルの確認) ファイルの確認) $ pgprof –nocheckjvm -jarg,jarg,-Xmx4G (プロファイラの実行) オプションの追加 ( -exe ./sample10 ) ※ この資料のコマンドをコピペした場合“-” が違う文字になっている場合があります ※ PGI16.1から使い⽅が変更されています -- 通常コンパイルでGUIにて動作 -(環境変数使用、または16.4以降最新版を使用/次ページ参照) 121 修正 プロファイラ(PGIGPU) source /usr/apps.sp3/isv/pgi/pgiset16.9.sh pgf95 -o sample10 sample10k.f wclock.c -Minfo=all -ta=nvidia pgprof PGIプロファイラの プロファイラの GUI 操作 (ver.16.4以降 以降) 以降 ブラウザから実行モジュールの選択 終了したらここを選択 新PGIプロファイラ プロファイラ はシングルのみ試験利用可能です/MPIは動きません は動きません はシングルのみ試験利用可能です 122 プロファイラ(GNU) 使用例 $ $ $ $ $ pgf95 -pg -o sample4 sample4.f90 <- PGIコンパイル ifort -p -o sample4 sample4.f90 <- intelコンパイラ gfortran –p –o sample4 sample4.f90 <- GNUコンパイラ ./sample4 <- 実行 gprof sample4 gmon.out <- プロファイラの実行 Flat profile: Each sample counts as 0.01 seconds. % cumulative self time seconds seconds calls 38.30 0.92 0.92 1 36.64 1.81 0.88 1 25.40 2.42 0.61 1 0.00 2.42 0.00 1 : self s/call 0.92 0.88 0.61 0.00 total s/call 0.92 0.88 0.61 2.42 name calc_ input_ setini_ MAIN__ 123 プロファイラ(並列) 並列対応プロファイラ > source /usr/apps.sp3/isv/pgi/pgiset15.10.sh > pgf95 –o s22 –Mprof=mpich,func sample11b.f wclock.c > which pgf95 /usr/apps.sp3/isv/pgi/15.10/linux86-64/2015/bin/pgf95 (コンパイラの場所を確認) ※ノード内でのみ使用してください。 ノード間は動作しません > /usr/apps.sp3/isv/pgi/15.10/linux86-64/2015/mpi/ ¥ mpich/bin/mpirun -np 4 ./s22 > ls pgprof.out pgprof1.out pgprof2.out pgprof3.out > pgprof -nocheckjvm -jarg,-Xmx4G –exe ./s22 注意 4並列以下で実行 本計算ではプロファイルしない 表示が遅いときはssh 表示が遅いときはssh 接続に接続に-Cを付加する 16.x は並列未対応のため、 は並列未対応のため、15.10 ため、15.10版を使用する 15.10版を使用する 124 修正 プロファイラ(並列;一般) > source set_ompi-1.6.5_p16.1.sh > mpif90 -o sample5a -pg sample5a.f > cat mpi_go #!/bin/bash if [ ! -d $OMPI_COMM_WORLD_RANK ] ; then mkdir $OMPI_COMM_WORLD_RANK fi cd $OMPI_COMM_WORLD_RANK ../sample5a > mpirun -np 4 mpi_go RANK= 1 ,DATA=TSUBAME RANK= 3 ,DATA=TSUBchac RANK= 2 ,DATA=TSUBchac RANK= 0 ,DATA=TSUBchac > ls 0 1 2 3 > cd 0 > ls gmon.out > gprof ../sample5a gmon.out (mpi_go) データがプロセス別に出力 プロセス別にgmon.outを保存 ランク別に保存するためにシェルを用意 openMPIの例 実行 mpi_go_opn <-- openMPI mpi_go_mva <-- mvapich2 mpi_go_mpc <-- mpich2 ランク別データを確認 プロファイラの実行 125 totalview (並列デバッガ) • icc -c wclock.c • mpif90 -g sample11a.f wclock.o (intelでの例) • totalview a.out 4並列以下で実行 表示が遅いときは ssh 接続に -Cを付加する Openmpiを選択 プロセス数を選択 選択したらOK 選択したら 126 totalview(その2) ②実行します ④変数を確認 ③ラインを確認 ①確認したい ラインをクリック ⑤プロセス別に確認 127 時間関数の組み込み (wclock.c) Fortranの標準的な時間ルーチンの代わりに、高い精度で計 測できるクロックルーチンを用意 $ cat wclock.c #include <sys/time.h> double second_() { struct timeval tm; struct timezone tmz; int i; i = gettimeofday(&tm,&tmz); return ( (double) tm.tv_sec + (double) tm.tv_usec * 1.e-6 ); } $ pgf95 -o sample sample.f wclock.c として一緒にコンパイル 128 時間計測 詳細計測用にミリ秒まで計測できる時間ルーチンを使用 並列化時の性能を求めるため詳細な時間計測の組み込み C------- for tune common /node/ipnum,nprocs real*8 second integer iblock,isize real*8 t00,t01,t02 real*8 tunetm(40,35) common /tuning/ iblock,isize,tunetm C------------t00=second() : 処理部分 この例はMPIにも対応 : t01=second() t02=t01 - t00 tunetm(21,ipnum+1) = tunetm(21,ipnum+1) + t02 この例の21は計測項目21番目の意味で、 ipnum+1が集計するプロセス番号です。 (初期設定が必要) c234567 real*4 t0(2),t1(2) real*8 a call etime(t0) a=0.0 do i=1,300 do j=1,100000 a=a+1.0D-6 enddo enddo call etime(t1) write(6,*) a,'time=',t1-t0 stop end ※Etimeを使用した例 129 計測コマンド 時間計測コマンドとして有名なのが、time,timexなど 各コマンドは使用しているマシンによっても若干異なる 以下timeコマンドの例 $ time sample real user sys 0m10.059s 0m10.030s 0m0.000s パフォーマンスアナライザ VTune Amplifier XE や PAPI による性能評価なども可能です。 【参照】 参照】TSUBAME2.5利用の手引き TSUBAME2.5利用の手引き <6.6 性能解析ツール> 性能解析ツール> http://tsubame.gsic.titech.ac.jp/docs/guides/tsubame2/html/programming.html#id11 130 性能表の作成 プログラムの性能を表にして、最適なノード(CPU)数を決定 これをEXCELで表にしてみます A B C 1 2 3 4 5 6 7 8 加速率 試験データを用意(例)して、小ステップで実行 格子サイズ 200x200x50として同じデータで計測 シングルで実行 ==> real 2m 2並列で実行 ==> real 1m10s 4並列で実行 ==> real 0m40s 8並列で実行 ==> real 0m25s 16 16並列で実行 ==> real 0m15s 14 32並列で実行 ==> real 0m10s 12 10 64並列で実行 ==> real 0m08s 8 6 4 2 0 系列1 0 =C2/Cn 性能表 20 40 CPU数 60 80 131 チューニング実習 (sample10) 1.基本情報の収集 1.基本情報の収集 > . /usr/apps.sp3/isv/pgi/pgiset15.10.sh > pgf95 -fastsse -Minfo=all -o sample10 sample10.f wclock.c > ./sample10 TIME= 10.38678717613220 30000.00000000162 > pgf95 -fastsse -Mprof=func -o sample10 sample10.f wclock.c > ./sample10 > pgprof -nocheckjvm -jarg,-Xmx4G 負荷を調査 プログラムの修正 ループの分割/融合 ブロック化 アンロール 2.コンパイルオプション試験 2.コンパイルオプション試験 > pgf95 -fastsse -Minfo=all -Minline=reshape -o sample10 sample10.f wclock.c > ./sample10 TIME= 10.60116195678711 30000.00000000162 3.実際のルーチンをチューニング 3.実際のルーチンをチューニング – サブルーチンを修正する – 手書きでインラインしてしまう > ./sample10 TIME= 1.361541986465454 30000.00000000162 このプログラムのGPU化にも挑戦してみてください。ひねりは必要ですが、さらに速くなります。 sample10K.f (ベクトル向けチューニング) 132 並列化(SMP)実習 (sample10,11) チューニング済 1.並列化前のプログラムを確認 1.並列化前のプログラムを確認 1.並列化前のプログラムを確認 1.並列化前のプログラムを確認 > pgf95 -fastsse -o sample10 sample10.f wclock.c > ./sample10 TIME= 10.38678717613220 30000.00000000162 FORTRAN STOP > pgf95 -fastsse -o sample10a sample10a.f wclock.c > ./sample10a TIME= 4.990428924560547 30000.00000000162 FORTRAN STOP 2.並列化 2.並列化( 並列化(自動並列) 自動並列) 2.並列化 2.並列化( 並列化(自動並列) 自動並列) > pgf95 –Mconcur -o sample10 sample10.f wclock.c > export NO_STOP_MESSAGE=yes > ./sample10 TIME= 10.13510107994080 30000.00000000162 > pgf95 –Mconcur -o sample11p sample11.f wclock.c > ./sample10a TIME= 5.010221004486084 30000.00000000162 > export OMP_NUM_THREADS=2 > ./sample10 TIME= 4.853079795837402 > export OMP_NUM_THREADS=4 > ./sample10 TIME= 2.486723184585571 > > export OMP_NUM_THREADS=2 > ./sample10a TIME= 3.060652971267700 30000.00000000116 > export OMP_NUM_THREADS=4 > ./sample10a TIME= 2.299788951873779 30000.00000000124 30000.00000000116 30000.00000000124 133 並列化(MPI)実習 (sample11) 1.並列化前のプログラムを確認 1.並列化前のプログラムを確認 > pgf95 -fastsse -Minfo=all -o sample11 sample11.f wclock.c > ./sample11 TIME= 1.323514938354492 30000.00000000162 > integer irank, isize,ierr real*8 SUM,RECV 2.並列化 2.並列化 基本ルーチンの記述 並列部分の確認 i方向に分割 -> 次のページ参照 インデックスの設定 -> 次のページ参照 ランク別の処理 総和関数の使用 結果をrank=0に集計 操作 結果を集めるランク 数(大きさ) call MPI_Reduce(sum,recv,1,mpi_double_precision,mpi_sum,0,MPI_COMM_WORLD,ierr) ※sumは各プロセス別の集計、recvは総合計で指定ランクのみ意味を持つ 134 並列化実習(2分割の考え方) Nst=1 Ned=5000 Nst=1 N (sample11) Ned=2500 Nst=2501 Ned=5000 N 2 M M SUM=SUM+A(M,N) SUM=SUM+A(M,N) SUM=SUM+A(M,N) ※ proc数=1 irank=0 結果 インデックスの計算例 Nsize=N/proc数 Nsize=N/proc数 Nst= Nsize*irank+1 Ned=Nsize*(irank+1) MPI_Reduce (recv) 結果 ※ proc数=2 irank=0,1 135 修正 並列化実習(実行) (sample11) 3.並列による実行 3.並列による実行 > > > > > > source set_ompi-1.6.5_p16.1.sh (source /usr/apps.sp3/isv/pgi/pgiset16.9.sh) vi sample11.f (配列サイズを少し大きくして、時間変数をR*4にします) export NO_STOP_MESSAGE=yes mpif90 -fastsse -Minfo=all -o sample11 sample11.f wclock.c mpirun -np 1 ./sample11 TIME= 11.17817 160000.0000000003 > mpirun -np 2 ./sample11 TIME= 6.010810 160000.0000000003 > mpirun -np 4 ./sample11 TIME= 4.481893 159999.9999999997 > ※インターラクティブは確認のみ 136 並列化実習(性能確認) (sample11) 4.性能確認 4.性能確認 加速率 1 2 4 8 16 TIME= TIME= TIME= TIME= TIME= 78.26612 43.58686 31.28252 21.40529 15.51969 18416640728.75977 18416640728.75977 18416640728.75977 18416640728.75977 18416640728.75977 ※一般利用( 一般利用(お試し) お試し)は10分まで 10分まで Uキューは2 キューは2ノードまで( ノードまで(ただし、空きがある場合のみ) ただし、空きがある場合のみ) Vキューは2 キューは2並列 まで 1 1.79 2.5 3.65 5.04 1 2 4 8 16 20 時間 78.26612 43.58686 31.28252 21.40529 15.51969 1.842E+10 1.842E+10 1.842E+10 1.842E+10 1.842E+10 加速率 理想 15 加速率 vi sample11a.f 分割しやすい数に変更 N=5000 --> --> N=12800 > source set_mpch-3.1_p16.1.sh > mpif90 –fastsse … > t2sub –q U –l selece=1:ncpus=1:mpiprocs=1 ./sample.sh > t2sub –q U –l selece=1:ncpus=2:mpiprocs=2 ./sample.sh : > t2sub –q U –l selece=2:ncpus=12:mpiprocs=12 ./sample.sh 理想 10 5 0 0 10 CPU数 20 137
© Copyright 2024 Paperzz