高速化チューニング - TSUBAME計算サービス

高速化チューニング
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