3.1. 巨大ループのループ分割¶
3.1.1. 動機¶
富士通Fortran/C/C++コンパイラはA64FXプロセッサの性能を引き出すために、ソフトウェアパイプライニングの最適化を活用します。 ただし、ソフトウェアパイプライニングはA64FXプロセッサの各種レジスタを通常よりも多く使用するため、対象ループのループボディが大きい場合、レジスタ不足によって本最適化を適用できない場合があります。
そこで、ソースプログラムにloop_fission_targetの最適化制御行(OCL)を挿入することによって、 巨大なループの自動分割をコンパイラに指示 できるケースがあります。
その結果、ソースプログラム上では大きなループが複数の小さなループとしてコンパイラで扱われることにより、ソフトウェアパイプライニングやレジスタ割り当ての最適化が促進されて、実行時間を短縮できる可能性があります。
3.1.2. 適用例¶
A64FX向けチューニング技術検討会 で示されたコード例を用いて、性能改善の例を以下に示します。 この例では、ループボディが大きいdo変数iiのループに対して、loop_fission_targetの最適化制御行を適用しています。
do ii=cumcnt(i,j,k,isp)+1,cumcnt(i+1,j,k,isp)
dh = up(1,ii,j,k,isp)*idelx-0.5-i
s0x(-2) = 0.D0
s0x(-1) = 0.5*(0.5-dh)*(0.5-dh)
s0x( 0) = 0.75-dh*dh
s0x(+1) = 0.5*(0.5+dh)*(0.5+dh)
s0x(+2) = 0.D0
dh = up(2,ii,j,k,isp)*idelx-0.5-j
s0y(-2) = 0.D0
s0y(-1) = 0.5*(0.5-dh)*(0.5-dh)
s0y( 0) = 0.75-dh*dh
s0y(+1) = 0.5*(0.5+dh)*(0.5+dh)
s0y(+2) = 0.D0
dh = up(3,ii,j,k,isp)*idelx-0.5-k
s0z(-2) = 0.D0
s0z(-1) = 0.5*(0.5-dh)*(0.5-dh)
s0z( 0) = 0.75-dh*dh
s0z(+1) = 0.5*(0.5+dh)*(0.5+dh)
s0z(+2) = 0.D0
i2 = int(gp(1,ii,j,k,isp)*idelx)
dh = gp(1,ii,j,k,isp)*idelx-0.5-i2
inc = i2-i
s1_1 = 0.5*(0.5-dh)*(0.5-dh)
s1_2 = 0.75-dh*dh
s1_3 = 0.5*(0.5+dh)*(0.5+dh)
smo_1 = -(inc-abs(inc))*0.5+0
smo_2 = -abs(inc)+1
smo_3 = (inc+abs(inc))*0.5+0
dsx(-2) = s1_1*smo_1
dsx(-1) = s1_1*smo_2+s1_2*smo_1
dsx( 0) = s1_2*smo_2+s1_3*smo_1+s1_1*smo_3
dsx(+1) = s1_3*smo_2+s1_2*smo_3
dsx(+2) = s1_3*smo_3
i2 = int(gp(2,ii,j,k,isp)*idelx)
dh = gp(2,ii,j,k,isp)*idelx-0.5-i2
inc = i2-j
s1_1 = 0.5*(0.5-dh)*(0.5-dh)
s1_2 = 0.75-dh*dh
s1_3 = 0.5*(0.5+dh)*(0.5+dh)
smo_1 = -(inc-abs(inc))*0.5+0
smo_2 = -abs(inc)+1
smo_3 = (inc+abs(inc))*0.5+0
dsy(-2) = s1_1*smo_1
dsy(-1) = s1_1*smo_2+s1_2*smo_1
dsy( 0) = s1_2*smo_2+s1_3*smo_1+s1_1*smo_3
dsy(+1) = s1_3*smo_2+s1_2*smo_3
dsy(+2) = s1_3*smo_3
i2 = int(gp(3,ii,j,k,isp)*idelx)
dh = gp(3,ii,j,k,isp)*idelx-0.5-i2
inc = i2-k
s1_1 = 0.5*(0.5-dh)*(0.5-dh)
s1_2 = 0.75-dh*dh
s1_3 = 0.5*(0.5+dh)*(0.5+dh)
smo_1 = -(inc-abs(inc))*0.5+0
smo_2 = -abs(inc)+1
smo_3 = (inc+abs(inc))*0.5+0
dsz(-2) = s1_1*smo_1
dsz(-1) = s1_1*smo_2+s1_2*smo_1
dsz( 0) = s1_2*smo_2+s1_3*smo_1+s1_1*smo_3
dsz(+1) = s1_3*smo_2+s1_2*smo_3
dsz(+2) = s1_3*smo_3
dsx(-2:2) = dsx(-2:2)-s0x(-2:2)
dsy(-2:2) = dsy(-2:2)-s0y(-2:2)
dsz(-2:2) = dsz(-2:2)-s0z(-2:2)
!OCL UNROLL('FULL')
do kp=-2,2
do jp=-2,2
pjtmpx = 0.D0
pjtmpy = 0.D0
pjtmpz = 0.D0
dstmpx = (s0y(jp)+0.5*dsy(jp))*s0z(kp) &
+(0.5*s0y(jp)+fac*dsy(jp))*dsz(kp)
dstmpy = (s0x(jp)+0.5*dsx(jp))*s0z(kp) &
+(0.5*s0x(jp)+fac*dsx(jp))*dsz(kp)
dstmpz = (s0x(jp)+0.5*dsx(jp))*s0y(kp) &
+(0.5*s0x(jp)+fac*dsx(jp))*dsy(kp)
do ip=-2,1
pjtmpx = pjtmpx-q(isp)*delx*idelt*dsx(ip)*dstmpx
pjtmpy = pjtmpy-q(isp)*delx*idelt*dsy(ip)*dstmpy
pjtmpz = pjtmpz-q(isp)*delx*idelt*dsz(ip)*dstmpz
pjx(ip+1,jp,kp) = pjx(ip+1,jp,kp)+pjtmpx
pjy(ip+1,jp,kp) = pjy(ip+1,jp,kp)+pjtmpy
pjz(ip+1,jp,kp) = pjz(ip+1,jp,kp)+pjtmpz
enddo
enddo
enddo
enddo
!OCL LOOP_FISSION_TARGET(LS)
do ii=cumcnt(i,j,k,isp)+1,cumcnt(i+1,j,k,isp)
...
enddo
改善前および改善後コードのサイクルアカウンティング測定結果を下記グラフに示します。 なお、性能測定条件は以下のとおりです。
cumcnt(i+1,j,k,isp) - cumcnt(i,j,k,isp) = 20
改善前(左のグラフ)に対して改善後(右のグラフ)の測定結果では、整数演算ビジー時間や整数演算待ち時間が大幅減少し、実行時間が44%減ったことが分かります。 なおL1Dキャッシュビジー時間やL1Dキャッシュアクセス待ち時間の減少は、ループ分割によってプロセッサのレジスタスピル/フィル動作が減った効果と考えられます。


3.1.3. 実例¶
A64FX向けチューニング技術検討会 にて、この種の事例が以下のとおり紹介されています。
3.1.4. 参考資料¶
注意: 上記ドキュメントの参照には スーパーコンピュータ「富岳」利用者ポータル のアクセス権が必要です。