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キャッシュアクセス待ち時間の減少は、ループ分割によってプロセッサのレジスタスピル/フィル動作が減った効果と考えられます。

_images/elecur.29503716.0.png _images/elecur.29503716.2.png

3.1.3. 実例

A64FX向けチューニング技術検討会 にて、この種の事例が以下のとおり紹介されています。

3.1.4. 参考資料

注意: 上記ドキュメントの参照には スーパーコンピュータ「富岳」利用者ポータル のアクセス権が必要です。