45
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6 理化学研究所計算科学研究機構(@神戸) 似鳥啓吾(にたどりけいご)@k_nitadori

Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

Xeon PhiとN体計算コーディング x86/x64最適化勉強会6 理化学研究所計算科学研究機構(@神戸) 似鳥啓吾(にたどりけいご)@k_nitadori

Page 2: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

簡単に自己紹介 ²  神戸の京コンの建物でポスドクしています

³  09年3月、東大理学天文で博士号(一応天体物理学者) ³  指導教員はGRAPEで有名な牧野淳一郎 ³  ポスドク5年目(3つ目)

²  専門(自己申告):N体計算の高速化、並列化、 アルゴリズム改良、良い実装を作る ³  SSE/AVX/CUDA/MPI/OpenMPあたりはカバー ³  最近はXeon phiやHaswellなど ³  アセンブラの知識:組み込み関数でSIMD書いたり コンパイラの吐いたasmを眺める程度

³  「京」向けのチューンも

Page 3: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

ゴードン・ベル賞に関して ²  スパコン上でのアプリケーションに対する論文賞

³  ベンチマークランキング(TOP500, HPCC)ではない ³  冬にはスパコンを確保し計算を走らせたい ³  春に12ページぐらいの論文を投稿 ³  夏にはfinalist/不採択の結果通知がくる ³  秋のSC学会で口頭発表、それから受賞論文の発表

²  似鳥が入った論文の成績 ³  09年:長崎のGeForceクラスタで価格性能部門賞、 10年には同部門佳作賞 (Honorable Mansion)

³  12年:「京」で受賞(部門なしの単独受賞) ³  06、07、08、13年には落選

Page 4: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

今日話す内容 ²  どうやって40~60分も間を持たせよう、、、

³  適当に割り込み、つっこみお願いします ²  専門のN体計算のお話

³  高次積分法で高精度に計算するお話 ®  Xeon Phi Nativeで実装したよ

³  テーブル参照で任意のforce shapeを実現するお話 ®  SSE/AVXでテーブル参照を頑張った

²  その際の若干トリッキーな最適化の話題 ³  普段のセミナーで「そんな話嬉しそうに延々とされても 困ります」といわれるような話題をいくつか

³  Xeon Phi (KNC)とAVX-512の命令セットの概観

Page 5: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

N体計算の支配方程式

²  式はひとつ(話題は無数) ²  減算3回、積和6回、乗算3回、逆数平方根1回 ²  i-loopとj-loopの二重ループ、O(N2) ²  j-粒子がi-粒子を引っ張る ²  i-並列とj-並列のふたつの並列度

³  i-並列ではj-粒子の放送によってメモリ帯域を節約 ³  j-並列では最後に部分力の総和演算が必要

ないし(発散を避けるために)  

Page 6: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

コードのほうが分り易いという人用

²  流用資料につき混合精度 ²  外側ループのomp parallel化は自明に近いと思う

²  レジスタが余っていたら外側ループをアンロールしよう

²  j-粒子がキャッシュに収まるようなブロック化/並列化も有効

4 const double posi[][3] ,

5 const double posj[][3] ,

6 const float mj[] ,

7 const float eps2 ,

8 double acci[][3])

9 {

10 for(int i=0; i<ni; i++){

11 const double xi = posi[i][0];

12 const double yi = posi[i][1];

13 const double zi = posi[i][2];

14 double ax = 0.0;

15 double ay = 0.0;

16 double az = 0.0;

17 for(int j=0; j<nj; j++){

18 const float dx = float(posj[j][0] - xi);

19 const float dy = float(posj[j][1] - yi);

20 const float dz = float(posj[j][2] - zi);

21 const float r2 = eps2 + dx*dx + dy*dy + dz*dz;

22 const float ri2 = 1.0f / r2;

23 const float mri1 = m[j] * sqrtf(ri2);

24 const float mri3 = mri1 * ri2;

25 ax += double(mri3 * dx);

26 ay += double(mri3 * dy);

27 az += double(mri3 * dz);

28 }

29 acc[i][0] = ax;

30 acc[i][1] = ay;

31 acc[i][2] = az;

32 }

33 }

このぐらいに書いておけば、コンパイラが「無駄なコード」を吐くことはまずない。コンパイラとオプションによっては、部分的に SIMD命令を使ってくれることもあるだろうが、得られる性能は手動で(インラインアセンブラか組み込み関数で)SIMD化したものに比べればかなり見劣りするものとなる。コンパイラベースの SIMD化が難しいのは、そもそもは高級言語での「型」と CPUが扱う「語」が、スカラー算のときは一致していたのが、CPU の側だけ SIMD 命令に行こうとしてももはや一致しないからである。このとき言語の側の「型」の方も拡張して SIMD命令側に対応するものを用意したのがコンパイラ提供の「組み込み関数 (intrinsics)」であり、これを用いて並列度を明示的にに指定してプログラムすることで、実行効率の高いコードを作ることができる(ただしアーキテクチャ間の移植性は失われる)。この辺は後でもう少し議論することとして、これから x86プロセッサにの SIMD拡張の歴史を簡単に振り返ってみる。

2 x86プロセッサの SIMD拡張の歴史もともと x86プロセッサには x87と呼ばれる浮動小数点命令がオプションで用意されており、これは 80-bitのスタック型のレジスタを8本備えたものでる。オプションということで、80386の時代までは FPUをコプロセッサとして別のチップで買ってきて、ソケットに増設するといったスタイルだった。80486以降は下位機

2

Page 7: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

高次の方法(4次のHermite積分法) ²  加速度の一階微分(jerk)も直接計算

³  コスト:「倍にはならない」 ²  補間多項式を積分

³  Hermite補間 ³  積分の始点と終点で、実質4点分の情報

²  予測子修正子法 ³  未来の座標はテイラー展開で予測 ³  未来の加速度とjerkを予測子から計算 ³  補間多項式から修正子を構築 ³  反復してもいいけどしなくても4次精度

®  Runge-Kuttaより高効率

t

f

Δv i+1 i

もっと高階微分も計算できる?(できます)

Page 8: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

高階微分の計算

²  2階微分(snap)まで使って6次精度 ²  3階微分(crackle)まで使って8次精度

³  次の微分にはpopと名前が付いている ²  高次のものほど

³  レジスタ消費は多い ³  積和比率が高い(逆数平方根は一回) ³  同期オーバーヘッドが相対的に小さい

38  ops 60  ops

97  ops 144  ops

Acceleration: Jerk:

Snap:

Crackle:

20 Chapter 2 Sixth- and eighth-order Hermite integrator

The above description is for the original individual timestep algorithm, and weusually use the so-called blockstep algorithms, in which the timesteps are quantizedto powers of two so that particles of the same stepsize share exactly the same time(McMillan 1986). In this way, we can calculate forces on these particles in parallel.

In the following, we present the force calculation formula, predictor, corrector,timestep criterion and initialization procedure, in this order.

2.2.2 Direct calculation of higher order derivatives

The gravitational acceleration from a particle j on a particle i and its first threetime derivatives are expressed as

Aij = mjrij

r3ij

, (2.1)

J ij = mjvij

r3ij

! 3!Aij , (2.2)

Sij = mjaij

r3ij

! 6!J ij ! 3"Aij , (2.3)

Cij = mjjij

r3ij

! 9!Sij ! 9"J ij ! 3#Aij . (2.4)

Here, we call the first four time derivatives of the acceleration jerk, snap, crackle andpop, and !, " and # are given by

! =rij · vij

r2ij

, (2.5)

" =|vij |2 + rij · aij

r2ij

+ !2, (2.6)

# =3vij · aij + rij · jij

r2ij

+ !(3" ! 4!2), (2.7)

where ri, vi, ai, ji and mi are the position, velocity, total acceleration, total jerk andmass of particle i, and rij = rj ! ri, vij = vj ! vi, aij = aj ! ai and jij = jj ! ji

(Aarseth, 2003).Table 2.1 shows the number of floating point operations needed to calculate the

terms from potential to crackle. We used the conversion factor 20 for the operationcounts for one division and one square root operations, following the convention intro-duced by Warren et al. (1997). Compared to the calculation up to jerk, the increaseof the operation count for higher order terms is rather modest. Even the calculationup to crackle is only about a factor of two more expensive than that up to jerk. Thus,if the eighth-order scheme allows two times larger timestep than that of the fourth-order scheme for the same accuracy, the eighth-order scheme would be more e!cient.Note that the CPU time is not directly proportional to the number of floating pointoperations, and therefore the actual e!ciency might be somewhat di"erent.

20 Chapter 2 Sixth- and eighth-order Hermite integrator

The above description is for the original individual timestep algorithm, and weusually use the so-called blockstep algorithms, in which the timesteps are quantizedto powers of two so that particles of the same stepsize share exactly the same time(McMillan 1986). In this way, we can calculate forces on these particles in parallel.

In the following, we present the force calculation formula, predictor, corrector,timestep criterion and initialization procedure, in this order.

2.2.2 Direct calculation of higher order derivatives

The gravitational acceleration from a particle j on a particle i and its first threetime derivatives are expressed as

Aij = mjrij

r3ij

, (2.1)

J ij = mjvij

r3ij

! 3!Aij , (2.2)

Sij = mjaij

r3ij

! 6!J ij ! 3"Aij , (2.3)

Cij = mjjij

r3ij

! 9!Sij ! 9"J ij ! 3#Aij . (2.4)

Here, we call the first four time derivatives of the acceleration jerk, snap, crackle andpop, and !, " and # are given by

! =rij · vij

r2ij

, (2.5)

" =|vij |2 + rij · aij

r2ij

+ !2, (2.6)

# =3vij · aij + rij · jij

r2ij

+ !(3" ! 4!2), (2.7)

where ri, vi, ai, ji and mi are the position, velocity, total acceleration, total jerk andmass of particle i, and rij = rj ! ri, vij = vj ! vi, aij = aj ! ai and jij = jj ! ji

(Aarseth, 2003).Table 2.1 shows the number of floating point operations needed to calculate the

terms from potential to crackle. We used the conversion factor 20 for the operationcounts for one division and one square root operations, following the convention intro-duced by Warren et al. (1997). Compared to the calculation up to jerk, the increaseof the operation count for higher order terms is rather modest. Even the calculationup to crackle is only about a factor of two more expensive than that up to jerk. Thus,if the eighth-order scheme allows two times larger timestep than that of the fourth-order scheme for the same accuracy, the eighth-order scheme would be more e!cient.Note that the CPU time is not directly proportional to the number of floating pointoperations, and therefore the actual e!ciency might be somewhat di"erent.

20 Chapter 2 Sixth- and eighth-order Hermite integrator

The above description is for the original individual timestep algorithm, and weusually use the so-called blockstep algorithms, in which the timesteps are quantizedto powers of two so that particles of the same stepsize share exactly the same time(McMillan 1986). In this way, we can calculate forces on these particles in parallel.

In the following, we present the force calculation formula, predictor, corrector,timestep criterion and initialization procedure, in this order.

2.2.2 Direct calculation of higher order derivatives

The gravitational acceleration from a particle j on a particle i and its first threetime derivatives are expressed as

Aij = mjrij

r3ij

, (2.1)

J ij = mjvij

r3ij

! 3!Aij , (2.2)

Sij = mjaij

r3ij

! 6!J ij ! 3"Aij , (2.3)

Cij = mjjij

r3ij

! 9!Sij ! 9"J ij ! 3#Aij . (2.4)

Here, we call the first four time derivatives of the acceleration jerk, snap, crackle andpop, and !, " and # are given by

! =rij · vij

r2ij

, (2.5)

" =|vij |2 + rij · aij

r2ij

+ !2, (2.6)

# =3vij · aij + rij · jij

r2ij

+ !(3" ! 4!2), (2.7)

where ri, vi, ai, ji and mi are the position, velocity, total acceleration, total jerk andmass of particle i, and rij = rj ! ri, vij = vj ! vi, aij = aj ! ai and jij = jj ! ji

(Aarseth, 2003).Table 2.1 shows the number of floating point operations needed to calculate the

terms from potential to crackle. We used the conversion factor 20 for the operationcounts for one division and one square root operations, following the convention intro-duced by Warren et al. (1997). Compared to the calculation up to jerk, the increaseof the operation count for higher order terms is rather modest. Even the calculationup to crackle is only about a factor of two more expensive than that up to jerk. Thus,if the eighth-order scheme allows two times larger timestep than that of the fourth-order scheme for the same accuracy, the eighth-order scheme would be more e!cient.Note that the CPU time is not directly proportional to the number of floating pointoperations, and therefore the actual e!ciency might be somewhat di"erent.

divとsqrtをそれぞれ  10演算と数えてある

Page 9: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

for(int j=0; j<nj; j++){ ! const dvec3 dr = pred[j].pos - posi; ! const dvec3 dv = pred[j].vel - veli; ! const dvec3 da = pred[j].acc - acci; ! const dvec3 dj = pred[j].jrk - jrki; ! ! const double r2 = eps2 + dr*dr; ! const double drdv = dr*dv; ! const double dvdv = dv*dv; ! const double drda = dr*da; ! const double dvda = dv*da; ! const double drdj = dr*dj; ! ! const double ri2 = 1.0 / r2; ! const double mri3 = pred[j].mass * ri2 * sqrt(ri2); ! const double alpha = drdv * ri2; ! const double beta = (dvdv + drda)*ri2 + alpha*alpha; ! const double gamma = (3.0*dvda + drdj)*ri2 + alpha*(3.0*beta - 4.0*alpha*alpha); ! ! dvec3 tmp1 = dv + (-3.0*alpha) * dr; ! dvec3 tmp2 = da + (-6.0*alpha) * tmp1 + (-3.0*beta) * dr; ! dvec3 tmp3 = dj + (-9.0*alpha) * tmp2 + (-9.0*beta) * tmp1 + (-3.0*gamma) * dr;!! acc += mri3 * dr;! jrk += mri3 * tmp1;! snp += mri3 * tmp2;! crk += mri3 * tmp3;!}

実装例 ²  書き散らかしたの置いたので見てね

³  https://github.com/nitadori/Hermite/blob/master/SRC/hermite8.h

³  誰か.ignoreその他教えてください ³  AVX/MIC/K版があります ³  積和が多いと嬉しいね

Page 10: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

独立時間刻み法 ²  重力のN体計算では近接遭遇が多発

³  いちいち全部の粒子の時間刻みを最小のものに切り揃えていては無駄が多い

³  粒子ごとにadaptiveに刻み幅を下げることを考える ®  とりあえず2の冪に ®  他の粒子の座標には「予測子」を使う

1/1  

1/2  

1/4  

1/8  

•  同時にNact個のactive粒子をアップデート •  全N粒子の予測子を計算:O(N) •  Nact個の粒子の加速度を計算:O(NactN) •  力を積分して修正子:O(Nact) •  <Nact>~N2/3

Page 11: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

続き(ちょっと物理数学) ²  単位箱にN個の粒子をランダムに置いたときの平均粒子間距離はN -1/3 ³  これはまあいいでしょう

²  そのときの最小の粒子ペア距離の期待値はN -2/3 ³  これを示すのは大変(誰かhelp!)

²  結果共有時間刻みではN個の粒子を同時に積分できたのが、独立時間刻みではN2/3個に ³  時間刻みは最近接粒子との距離に比例すると仮定 ³  N=1000に対し<Nact>=50ぐらい(系の中心集中度や積分次数にも依ります) ³  大幅に高速化したのはいいがi-粒子の並列度がそのまま食われてしまった

Page 12: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

時間刻みの決め方 ²  Hermite補間の際に得られた加速度の時間微分から次の時間刻みを決定

³  経験的な公式 ³  pは積分次数、ηは精度パラメタ

²  1/6乗や1/10乗が発生するのがちょっと嫌

³  しかも結果は2の冪に丸められる ³  というわけで最適化 ³  これでlibmathいらず

template <int N> !double pow_one_nth_quant(! const double x)!{! assert(x > 0.0); ! union{ ! double d;! unsigned long l; ! } m64; ! m64.d = x;! const int dec = 1023 % N;! const int inc = 1023 - 1023 / N; ! m64.l >>= 52; ! m64.l -= dec;! m64.l /= N; ! m64.l += inc;! m64.l <<= 52;! return m64.d;!}

Page 13: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

1ステップの手続き 1.  積分する粒子を選ぶ:O(Nact) or O(logN) (serial) 2.  全粒子の予測子を計算:O(N) (parallel) 3.  重力を計算:O(NactN) (parallel) 4.  修正子と新しい時間刻みを計算:O(Nact) (parallel) 5.  粒子を時間刻み順にソート:O(NactlogNact) (serial)

Page 14: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

そろそろXeon Phiの話 ²  Xeon Phi 5110P (Knights Corner)

³  1.053 GHz ³  60 core, 240 threads ³  512-bit FMA (16 DP flop/cycle), 1011 DP, 2022 SP Gflops

³  60 x (32KB L1I, 32KB L1D, 512KB L2) ²  32本の512-bit zmmレジスタ

³  レジスタ幅がキャッシュライン幅に追いついた(64-byte) ³  4スレッドで8KBi! ³  float x16 or double x8 ³  16-bitのマスクレジスタが8本 ³  xmmもymmもない(Pentium互換+X64+独自拡張)

Page 15: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

AVX-512の概観(ぱっと見) ²  次世代KNL (Knights Landing)から

³  普通のXeon/Core iにも搭載されるのだろうか? ²  XMM/YMMが復活、ZMMと共に32本

³  マスクレジスタ、swizzle、bcastもサポート ²  EVEX prefix

³  62hからの4-byte ²  KNC(現行のPhi)のベクトル命令をベースにSSE/AVXと下位互換を取るように整理したもの、といった感じ ³  数学関数の取り扱いが若干変わった

Page 16: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

プログラミングモデル ²  Native mode

³  Phiのカード上でLinuxが動いていてsshで入れる ³  ホストのディレクトリをNFS経由でマウントできる

®  icc -mmic hello.c ssh mic0 ./a.out

³  コードはintrinsicsとOpenMPで書いておく ®  zmmintrin.h (included from immintrin.h), micvec.h (for C++)

²  Offload mode ³  ホスト実行のコードの一部を#pragma offloadで切り出す ³  GPU的な使い方(生産性は高いとの主張) ³  似鳥は試したことないのでよくわかりません、 講習会とかもやってるっぽい

Page 17: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

ニーモニック ²  vaddpd zmm1{k1}, zmm2, zmm3/mem

³  zmm1 = zmm2 + zmm3/mem ³  k1: マスクレジスタで結果代入をマスクできる ³  zmm3: swizzleが使える!

®  dcba-> dcba (no swizzle), cdab (swap inner), badc (swap outer), dacb (cross prod), aaaa, bbbb, cccc, dddd (bcast)

³  mem: 放送と型変換(upconv)ができる! ®  DP: 8to8, 4to8, 1to8 ®  SP: 16to16, 4to16, 1to16 ®  適切にalignされていること

Page 18: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

load/store ²  ZMMレジスタとキャッシュラインサイズが64 byteで一致 ²  Aligned load/store

³  単にvmovapd ³  no-read, no-global-orderingのstreaming store命令も存在

²  Unaligned load/store ³  2ライン触るので2命令

®  v1  =  _mm512_loadunpacklo_pd(v1,  mt);  ®  v1  =  _mm512_loadunpackhi_pd(v1,  mt+64);  ®  warning  (uninitialized  v1)を消す方法ありませんか?  

²  Gather/Scatter ³  速度はともかく、命令が存在する ³  コストは触ったライン数に依存とのこと

Page 19: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

積和命令 ²  VFMADD{132|213|231}PD zmm1, zmm2, zmm3

³  zmm1 = zmm1 * zmm3 + zmm2 ³  zmm1 = zmm2 * zmm1 + zmm3 ³  zmm1 = zmm2 * zmm3 + zmm1

²  あと符号で4種類(*́д`*) ²  swizzle/メモリオペランドは第3opのみ使える ²  まあ、mulとadd/subで書いとけばコンパイラがやってくれるんだけど

²  intrinsicsは4オペランド形式 ²  swizzleと混ぜるときはmovの数が最小になるといいですね

Page 20: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

swizzle万歳 ²  レジスタ1本で4つの定数を格納できる ²  C++ラッパのほうが見た目がasmに近い

³  ただしswizzleメソッドがconstになってないというバグ仕様 ³  サンプルのasmはAT&T形式です、すみません

#include <micvec.h>!!F64vec8 poly3(F64vec8 x, F64vec8 coef){! return coef.aaaa() + x*(coef.bbbb() + x*(coef.cccc() + x*(coef.dddd())));!}!F64vec8 poly3(F64vec8 x, const double *coef){! return F64vec8(coef[0]) + x*(F64vec8(coef[1]) + x*(F64vec8(coef[2]) + x*(F64vec8(coef[3]))));!}!

poly3(F64vec8, F64vec8):!vmovdqa64 %zmm1{dddd}, %zmm2 !vfmadd213pd %zmm1{cccc}, %zmm0, %zmm2 !vfmadd213pd %zmm1{bbbb}, %zmm0, %zmm2 !vfmadd213pd %zmm1{aaaa}, %zmm2, %zmm0 !ret

poly3(F64vec8, double const*):!vbroadcastsd 24(%rdi), %zmm1 !vfmadd213pd 16(%rdi){1to8}, %zmm0, %zmm1 !vfmadd213pd 8(%rdi){1to8}, %zmm0, %zmm1 !vfmadd213pd (%rdi){1to8}, %zmm1, %zmm0 !ret

Page 21: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

Shuffle系の命令 ²  mask_blendとswizzleでかなりのことができる

³  vblendmps, vblendmpd ³  引数のマスクレジスタで要素を選択 ³  組み込み関数からだと即値指定(imm16/imm8)も可能だけど、実際は汎用レジスタ経由でマスクレジスタに飛ばされる

²  4語(単精度で128-bit、倍精度で256-bit)の単位をまたいでshuffleしたい場合 ³  vpermf32x4 zmm1{k1}, zmm2/mt, imm8

®  128-bitが4つあるのを、imm8で並び替え ³  vpermd zmm{k1}, zmm2, zmm3/mt

®  32-bitが16語あるのを任意に並び替え ®  indicatorはimmではなくてzmm2の各語下位4-bit

Page 22: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

応用:4x4行列転置 ²  上4語と下4語で同時に

static  inline  void  transpose_4zmm_pd(F64vec8  &v0,  F64vec8  &v1,  F64vec8  &v2,  F64vec8  &v3){          F64vec8  c1c0a1a0  =  _mm512_mask_blend_pd(0xaa,  v0,  v1.cdab());          F64vec8  c3c2a3a2  =  _mm512_mask_blend_pd(0xaa,  v2,  v3.cdab());          F64vec8  d1d0b1b0  =  _mm512_mask_blend_pd(0x55,  v1,  v0.cdab());          F64vec8  d3d2b3b2  =  _mm512_mask_blend_pd(0x55,  v3,  v2.cdab());                    F64vec8  aaaa  =  _mm512_mask_blend_pd(0xcc,  c1c0a1a0,  c3c2a3a2.badc());          F64vec8  bbbb  =  _mm512_mask_blend_pd(0xcc,  d1d0b1b0,  d3d2b3b2.badc());          F64vec8  cccc  =  _mm512_mask_blend_pd(0x33,  c3c2a3a2,  c1c0a1a0.badc());          F64vec8  dddd  =  _mm512_mask_blend_pd(0x33,  d3d2b3b2,  d1d0b1b0.badc());                    v0  =  aaaa;                                                                                                                                                    v1  =  bbbb;                                                                                                                                                    v2  =  cccc;                                                                                                                                                    v3  =  dddd;                                                                                                                                            }    

²  マスクレジスタを4本消費 ³  もっといい方法あったら教えてください

Page 23: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

用途 ²  インテルさんはSoA推奨だけど

³  x[N], y[N], z[N], m[N]みたいの ²  AoSにしたいこともよくあるわけです

³  {x, y, z, m}[N]みたの ³  高次積分のN体だともっとメンバ多いし

²  今回のN体コードの実装 ³  i-粒子4並列、j-粒子2並列で8-way SIMD ³  i-粒子:{x0, x1, x2, x3, x0, x1, x2, x3}

®  yとzも同様 ®  さっきの転置で作っておく

³  j-粒子:{x0, y0, z0, m0, x1, y1, z1, m1} ®  swizzleで放送

Page 24: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

コードはこんな感じ ²  vsubrpdという新命令で-(lhs-rhs)を計算

³  rhsしかswizzleできないので痒い所に手が届く ³  別にこう書かなくてもコンパイラがやってくれるけど

for(int j=jbeg; j<jend; j+=2){! const double *jptr = (double *)(&pred[j/2]);! _mm_prefetch((char *)(jptr + 16), _MM_HINT_T0);! _mm_prefetch((char *)(jptr + 24), _MM_HINT_T0);! F64vec8 jxbuf = *(__m512d *)(jptr + 0); ! F64vec8 jvbuf = *(__m512d *)(jptr + 8); !! const F64vec8 dx = -(xi - jxbuf.aaaa());! const F64vec8 dy = -(yi - jxbuf.bbbb());! const F64vec8 dz = -(zi - jxbuf.cccc());! const F64vec8 dvx = -(vxi - jvbuf.aaaa());! const F64vec8 dvy = -(vyi - jvbuf.bbbb());! const F64vec8 dvz = -(vzi - jvbuf.cccc());! ...!}

•  j-粒子のL1への手動プリフェッチは有効であった

•  ハードもコンパイラもL1へは無闇にPFするわけにもいかないため

Page 25: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

60コア240スレッドの使い方 ²  単純i-並列/j-並列で使うには多すぎる

³  今回の実装では、コア内4スレッドをi-並列に 60コアをj-並列にしてみた

®  環境変数KMP_AFFINITY=compactとしておくと、コア内4スレッドが連番に

®  OpenMPでは0から239のスレッド番号が取得できる ®  j-粒子が分割キャッシュに乗るという目論見 ®  affinityを切ると若干の性能低下 ®  部分力[Nact][60]はメモリに書き出して後から総和を取る

Page 26: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

性能 ² コードは倍精度の4/6/8次積分法

³ 相互作用あたり60/97/144演算とした ³ 横軸は粒子数N

² 比較用:Haswell i7 4C8T 3.40 GHz ³  217.6 Gflops peak ³  AVXで開発済みだったコードをGCC 4.81で -O2 -march=core-avx2(積和化された)

Page 27: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

Gflops値 ²  粒子数の多いところではHaswell16コア分ぐらい

²  GRAPEやGPUのような振る舞い

²  CPUとの転送は存在しないのだが、、、

Page 28: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

ステップあたりのμ秒 ²  200μ秒付近に壁がある

²  同期オーバーヘッドのようだ

Page 29: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

同期が遅い! ²  omp barrierで20μs、omp parallel {}で30μsぐらい 持っていかれる(実測) ³  Opteron 4 socket, 8 die, 64 coreのマシンより遅い ³  ステップあたり5回同期していたので納得できる

²  粒子ソートもHaswellの50倍ぐらい遅かった ³  同期と同様馬鹿にならない時間がかかっていた

²  Xeon Phiのボトルネック ³  個々のコアが貧弱(これは仕方ないかもだけど) ³  コア間通信が遅い(キャッシュが分散しているため)

®  ランタイムの改良で若干改善できるかも(同期の階層化) ²  なんか、殆どGPUだよね(今日のPhiの話はここまで)

Page 30: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

カットオフ関数がある計算 ²  宇宙論的なN体計算だと

³  遠方から(低周波)の重力は一様メッシュとFFTで ³  近距離(高周波)の重力は粒子から直接計算する方法がよく使われる

®  このとき到達距離が数メッシュ間隔ぐらいのカットオフ関数が掛かる

²  SSE/AVXで無理矢理テーブル参照で実装 ²  「京」でのGB賞ではこの関数を直接計算した

Page 31: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

具体的なかたち ²  教科書にあったもの

²  分岐を排除したもの ³  「京」ではこれを直接計算した

というように単純に線形に減少して打ち切られるという、というものである(教科書 p.270)。上記の文献では、その他に一様密度のものを S1分布、ガウシアンのものを S3分布としている。S1分布と S2分布はそれぞれ、CIC (cloud-in-cell)法と TSC ( triangularshape cloud)法に対応付けて考えることもできる。

2 Force shape

さて、S2クラウドがふたつあるとして、そのクラウド間に働く引力は、距離が 2以上離れていれば Newtonの定理により 1/R2 であるが、それ以外の場合は体積素片の相互作用を六重積分して求める必要がある。計算結果は教科書 p. 300に載っていて、

f(R) =

!"""""""#

"""""""$

1140

`224R ! 224R3 + 70R4 + 48R5 ! 21R6

´(0 " R " 1)

1140

„12R2

! 224 + 869R ! 840R2 + 224R3 + 70R4 ! 48R5 + 7R6

«(1 " R " 2)

1R2

(2 " R)

,

(2)

ある種の spline softeningのような形をしているが、何かをスプラインで近似したのではなく、S2分布に対する厳密解であることには注意。

P3M 法の PP パート(つまりは S2 soften された PM forceの Newton 重力からの残差)での重力相互作用へのカットオフ関数 

ai =%

j !=i

mj(rj ! ri)|rj ! ri|3

gP3M(|rj ! ri|)/!), (3)

として表現する場合は、 

gP3M(R) =

!"""""#

"""""$

1 ! 1140

`224R3 ! 224R5 + 70R6 + 48R7 ! 21R8

´(0 " R " 1)

1 ! 1140

`12 ! 224R2 + 869R3 ! 840R4 + 224R5 + 70R6 ! 48R7 + 7R8

´(1 " R " 2)

0 (2 " R)

,

(4)

とあらわせる。

2

ポテンシャルも写経:

!(R) =

!"""""""#

"""""""$

1140

ˆ208 ! 112R2 + 56R4 ! 14R5 ! 8R6 + 3R7

˜(0 " R " 1)

1140

»12R

+ 128 + 224R ! 448R2 + 280R3 ! 56R4 ! 14R5 + 8R6 ! R7

–(1 " R " 2)

1R

(2 " R)

.

(5)

3 Optimization

このままでは演算量も多く分岐もあるため、PP相互作用の度にこれを計算するのはさすがに無視できないコストとなる。そこでモダンな汎用計算機のための効率的な式変形を試みてみよう。

S ! max(0, R " 1), (6)

として、

gP3M(R) = 1 + R3

%"8

5+ R2

%85

+ R

%"1

2+ R

%"12

35+ R

320

&&&&

" S6

%335

+ R

%1835

+ R15

&&(0 # R # 2) (7)

と変形すれば、8回の積和算でこの関数が計算でき、分岐のひとつは max関数に落とせた(maxはふつうハードウェア実装されている)。重力相互作用の計算では、上の式を R

で割ったものを評価しておけば、乗算数で少し得する。(2 # R)の場合についての分岐は比較の結果でマスクするか、あるいは丸め誤差に注意して(逆数平方根に近似命令を使っても、4.0の逆数平方根は誤差なく 0.5となるだろう)

R $ min(R, 2), (8)

とすれば消すことができる。

4 Quadrupole Moment

ツリー法では精度が必要なときは "をやみくもに小さくするよりは四重極やそれ以上のモーメントを入れたほうが計算コストは小さく済む。しかしカットオフ関数が入ると、四

3

ポテンシャルも写経:

!(R) =

!"""""""#

"""""""$

1140

ˆ208 ! 112R2 + 56R4 ! 14R5 ! 8R6 + 3R7

˜(0 " R " 1)

1140

»12R

+ 128 + 224R ! 448R2 + 280R3 ! 56R4 ! 14R5 + 8R6 ! R7

–(1 " R " 2)

1R

(2 " R)

.

(5)

3 Optimization

このままでは演算量も多く分岐もあるため、PP相互作用の度にこれを計算するのはさすがに無視できないコストとなる。そこでモダンな汎用計算機のための効率的な式変形を試みてみよう。

S ! max(0, R " 1), (6)

として、

gP3M(R) = 1 + R3

%"8

5+ R2

%85

+ R

%"1

2+ R

%"12

35+ R

320

&&&&

" S6

%335

+ R

%1835

+ R15

&&(0 # R # 2) (7)

と変形すれば、8回の積和算でこの関数が計算でき、分岐のひとつは max関数に落とせた(maxはふつうハードウェア実装されている)。重力相互作用の計算では、上の式を R

で割ったものを評価しておけば、乗算数で少し得する。(2 # R)の場合についての分岐は比較の結果でマスクするか、あるいは丸め誤差に注意して(逆数平方根に近似命令を使っても、4.0の逆数平方根は誤差なく 0.5となるだろう)

R $ min(R, 2), (8)

とすれば消すことができる。

4 Quadrupole Moment

ツリー法では精度が必要なときは "をやみくもに小さくするよりは四重極やそれ以上のモーメントを入れたほうが計算コストは小さく済む。しかしカットオフ関数が入ると、四

3

Page 32: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

一応図示 ²  赤がポテンシャルのカットオフ

²  緑が今回欲しい力のカットオフ

²  残り2本は多重極展開するとき使う物 ³  float4のtexfetch1Dで 一気にやろうとか考えてた

Page 33: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

テーブル参照で関数を評価 ² 普通に考えれば(int)(scale * r)でindexを生成して適当な次数で補間してf(r)を計算

² しかし、r2 → f(r)/r3 を一発で評価するのが最速に思える ³ サンプル間隔も最適に近い物を選びたい ³ 何をindexに使うか、どうやって得るか? ³ 若干トリッキーな方法をとった

Page 34: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

indexの生成 ²  座標は適当にスケールしておく ²  s = 2.0f + r2を計算 ²  指数部の下位4-bitと仮数部の上位6-bitを用いる

³  17-bit右シフトだけでいい ³  あとは1次補間で必要な精度に

Author's personal copy

including the Plummer softening, S2 softening, etc. can be set. Thefunction f !r" is assumed to shape; almost constant at r < !, rapidlydecreases at larger r, and reaches zero at r # rcut. Such assumptionsare satisfied in the inter-particle force calculations of PPPM orTreePM methods.

In order to calculate central forces with an arbitrary shape in Eq.(2), we refer to a pre-calculated look-up table of f !r"=r and use thelinear interpolation between the sampling points. In Sections 3.4.1and 3.4.2, we describe our scheme to construct the look-up table,and procedure to calculate the force by using the look-up tablewith the AVX instructions, respectively.

3.4.1. Construction of an optimized look-up tableIn terms of numerical accuracy, the look-up table is preferred to

have a large number of sampling points between 0 6 r 6 rcut. Onthe other hand, the size of the look-up table should be as smallas possible to avoid cache misses for fast calculations. Thus, it isimportant how to choose sampling points of the look-up table inorder to satisfy such exclusive requirements: accuracy and fast cal-culations of forces.

In many previous implementations, sampling points of thelook-up table are chosen so that the sampling points have equalintervals in a squared inter-particle distance r2 at 0 < r < rcut.However, the sampling with equal intervals in r2 is not a goodchoice, because it has coarser intervals at a smaller inter-particledistance, and the force shape at r K ! is poorly sampled if the num-ber of sampling points is not large enough, while the shape atr ’ rcut is sampled fairly well, or even redundantly (see the top pa-nel of Fig. 6). Typically speaking, tens of thousand sampling pointsin the region 0 < r < rcut are required to assure the sufficient forceaccuracy if sampling with equal intervals in r2 is adopted. Suchlook-up tables need several hundred kilobytes in single-precision,and do not fit into a low-level cache memory.

The desirable sampling of the force shapes, therefore, shouldhave almost equal intervals in r at short distances r K !, and inter-

vals proportional to r (or equal intervals in ln r) at long distances. Inthe following, we realize such a sampling by adopting rather a newbinning scheme, with which we can compute the force efficiently.

Here, we consider to construct a look-up table of f !r"=r in therange of 0 < r < rcut. In our binning scheme, the indices of thelook-up table are calculated by directly extracting the fractionand the exponent bits of the IEEE754 format of squared inter-parti-cle distances. First, the squared distance r2 is affine-transformed toa single-precision floating-point number s $ r2!smax % 2"=r2

cut & 2 sothat s is in the range of smin < s < smax, where smin $ 2 and smax $22E!2% 1=2F". Here, E and F are the pre-defined positive integers,

and the numbers of exponent and fraction bits extracted in comput-ing the indices of the look-up table, respectively. Binary expressionsof smin and smax in the IEEE754 format of single-precision (32-bit) inthe case of E # 4 and F # 6 are shown in Table 4. Except that themost significant bit of the exponent part is always 1, all the bitsof smin are 0, and as for smax, only the lower E bits of the exponentand the higher F bits of the fraction are 1. Next, the indices of thelook-up table for the squared distances r2 or s are computed byextracting the lower E bits of the exponent and the higher F bitsof the fraction of s (underlined portion of exponent and fraction bitsin Table 4) and and reinterpreting it as an integer. This procedurecan be done by applying a logical right shift by 23% F bits, and a bit-wise-logical AND with 2E&F % 1 to s. It should be noted that theresulting size of the look-up table is 2E&F .

An affine-transformed squared distance at a sampling pointwith an index specified by a lower E exponent bits bE and an upperF fraction bits bF is expressed as

sbE ;bF # 2bE&1 1& bF

2F

! "0 6 bE < 2E; 0 6 bF < 2F# $

: !3"

The ratio between inter-particle distances whose affine-trans-formed squared distances are s!bE&1";bF and sbE ;bF is given by

r!bE&1";bF

rbE ;bF

#s!bE&1";bF % 2

sbE ;bF % 2

! "1=2

’ 21=2; !4"

where bE ' 1 is assumed for the last approximation. The intervalbetween inter-particle distances whose affine-transformed dis-tances are sbE ;!bF&1" and sbE ;bF is calculated as

rbE ;!bF&1" % rbE ;bF #sbE ;!bF&1" % 2

smax % 2

! "1=2

%sbE ;bF % 2smax % 2

! "1=2

’ 1!2F & bF"1=2

2bE&1=2F&2

smax % 2

!1=2

; !5"

where we also assume bE ' 1 and F ' 1 for the last approximation.Therefore, the sampling points with the same fraction bits are dis-tributed uniformly in logarithmic scale, and those with the sameexponent bits are aligned uniformly in linear scale unless the frac-tion bit is small.

As an example, we illustrate how the sampling points of thelook-up table depend on the pre-defined integers E and F inFig. 4. We first see the cases in which either of E and F is zero, in

Fig. 3. A schematic illustration of the force loop. Each set of four boxes indicates thelower 128-bit of a YMM register. Each box contains a single-precision floating-pointnumber. Note that some of aliases are reused to store data other than described inTable 3.

Table 4s-values, their exponent and fraction bits in the IEEE754 expressions, and their indicesin the table for r # 0, rcut=2 and rcut in the case of E # 4 and F # 6 (underlined portionof exponent and fraction bits).

r s Exponentbits

Fraction bits Index

0 2 (smin) 10000000 00000000000000000000000 0rcut=2 3:2514( 104 10001101 11111100000001100000000 895rcut 1:3005( 105 10001111 11111100000000000000000 1023

(smax)

A. Tanikawa et al. / New Astronomy 19 (2013) 74–88 79

Author's personal copy

In Fig. 6, we compare the conventional binning with equal inter-vals in squared distances to our binning with E ! 4 and F ! 2 (i.e. 64sampling points), for the S2-force shape (Hockney and Eastwood,1981) used in the PPPM scheme. Although we adopt F ! 5 in therest of this paper, we set F ! 2 here just for good visibility of the dif-ference of the two binning schemes. It should be noted that thenumber of sampling points is the same (64) in both schemes. Com-pared with the conventional binning scheme in the top panel, ourbinning scheme can faithfully reproduce the given functional formeven at distances smaller than the gravitational softening length.

3.4.2. Procedure of force calculationIn calculating the arbitrary central forces, the data of i- and j-

particles are stored in the structures Ipdata and Jpdata, respec-tively, in the same manner as described in the case for calculatingthe Newton’s force, except that the coordinates of i- and j-particlesare scaled as

~ri !ri

rcut=!!!!!!!!!!!!!!!!!!smax " 2p ; #8$

so that we can quickly compute the affine-transformed squared in-ter-particle distances between i- and j-particles. As in the case ofthe Newton’s force, we compute the forces of four i-particles ex-erted by two j-particles using the AVX instructions. Using the scaledpositions of the particles, the calculation of the forces is performedin the force loop as follows;

(i) Calculate an affine-transformed distance between i- andj-particles, s, as

s !min j~rj " ~rij2 % 2; smax

" #; #9$

where the function ‘‘min’’ returns the minimum value amongarguments.(ii) Derive an index k of the look-up table from the affine-trans-formed squared distance, s, computed in the previous step byapplying a bitwise-logical right shift by 23" F bits and reinter-preting the result as an integer.(iii) Refer to the look-up table to obtain G0

k and G1k . Note that the

address of the pointer to fcut is decremented by 1&(30-(23-F)) in advance (see line 24 in List 5) to correct the effect of themost significant exponential bit of s.(iv) Derive an affine-transformed distance sk that correspondsto the k-th sampling point rk by applying a bitwise-logical leftshift by 23" F bits to k and reinterpreting the result as a sin-gle-precision floating-point number.(v) Compute the value of f #jrj " rij$=jrj " rij by the linear inter-polation of G0

k and G0k%1. Using the values of G0

k and G1k , the inter-

polation can be performed as

f #jrj " rij$jrj " rij

! G0k % G1

k s" sk# $: #10$

(vi) Accumulate scaled ‘‘forces’’ on i-particles as

~ai !XN

j

mjf #jrj " rij$jrj " rij

#~rj " ~ri$ #11$

After the force loop, the scaled ‘‘forces’’ are rescaled back as

ai !rcut!!!!!!!!!!!!!!!!!!

smax " 2p ~ai: #12$

The actual code of the force loop for the calculation of the centralforce with an arbitrary force shape is shown in List 5. Note that bit-wise-logical shift instructions such as VPSRLD and VPSLLD can beoperated only to XMM registers or the lower 128-bit of YMM regis-ters. In order to operate bitwise-logical shift instructions to data inthe upper 128-bit of a YMM register, we have to copy the data to thelower 128-bit of another YMM register. Bitwise-logical shift opera-tions to the upper 128-bit of YMM registers are supposed to beimplemented in the future AVX2 instruction set. Also note thatwe cannot refer to the look-up table in a SIMD manner and haveto issue the VLOADLPS and VLOADHPS instructions one by one(see lines 89–92 and 94–97 in List 5). Except for those operations,all the other calculations are performed in a SIMD manner usingthe AVX instructions.

Although the AVX instruction set takes the non-destructive 3-operand form, the copy instruction between registers appeared inthe code above, which was intended to avoid the inter-registerdependencies.

3.5. Parallelization on multi-core processors

On multi-core processors, we can parallelize the calculations ofthe forces of i-particles for both of the Newton’s force and arbitrarycentral forces using the OpenMP programming interface by assign-ing a different set of four i-particles onto each processor core. List 6shows a code fragment for the parallelization of the computationsof the Newton’s force. The calculation of an arbitrary force can beparallelized similarly to that of Newton’s force.

10 -2

10 -1

10 0

10 1

10 2

10 3

10 4

f (r)

r cut3 /

r

Conventional

10 -2

10 -1

100

101

102

103

104

10 -3 10 -2 10 -1 10 0

f (r)

r cut3 /

r

r / rcut

Presented

Fig. 6. Binning of f #r$=r in the conventional scheme with 64 constant intervals in r2

(top panel) and in our scheme with E ! 4 and F ! 2 (bottom panel) between '0; rcut(.Although we adopt F ! 5 elsewhere in this paper, we set F ! 2 here for viewability.R#r; !$ " R#r; rcut$ is assumed as a functional form of f #r$, in which R#r;g$ is the S2-profile (Hockney and Eastwood, 1981) (see Eq. (16)). Solid lines indicate the shapeof f #r$=r. Vertical dashed lines in both panels are the locations of the gravitationalsoftening length !.

A. Tanikawa et al. / New Astronomy 19 (2013) 74–88 81

Page 35: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

AVXでのテーブル参照 ²  GatherはAVX2から(しかも遅い) ²  テーブルの実体は補間用の係数を含めてfloat table[1024][2];

²  indexはpextrwないしL1経由で汎用レジスタに転送

²  gatherの代わりに(movlps, movhps)^2, vinsertf128

²  あとは適当にシャッフル

Page 36: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

適当にシャッフル d5 f5 d4 f4 d1 f1 d0 f0

d7 f7 d6 f6 d3 f3 d2 f2

f0 f1 f2 f3 f4 f5 f6 f7

d0 d1 d2 d3 d4 d5 d6 d7

vshufps 0x88 vshufps 0xdd

0x88 = 2020(4), 0xdd = 3131(4)

Page 37: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

AVX2のコード(GCC) for(j=0; j<nj; j+=2){! v8sf xj = __builtin_ia32_shufps256(jp, jp, 0x00);! v8sf yj = __builtin_ia32_shufps256(jp, jp, 0x55);! v8sf zj = __builtin_ia32_shufps256(jp, jp, 0xaa);! v8sf mj = __builtin_ia32_shufps256(jp, jp, 0xff);! jp = *(v8sf *)(jpdata+=2);!! v8sf dx = xj - xi, dy = yj - yi, dz = zj - zi;! v8sf r2 = ((two + dx*dx) + dy*dy) + dz*dz;! r2 = __builtin_ia32_minps256(r2, r2cut);! v8si r2_sr = __builtin_ia32_psrldi256((v8si)r2, 23-FRC_BIT);! v8si r2_sl = __builtin_ia32_pslldi256(r2_sr, 23-FRC_BIT);! unsigned int idx[8] __attribute__((aligned(32)));! *(v8si *)idx = r2_sr;! const long long *ptr = (long long *)fcut;! v4di tbl_0145 = {ptr[idx[0]], ptr[idx[1]], ptr[idx[4]], ptr[idx[5]]};! v4di tbl_2367 = {ptr[idx[2]], ptr[idx[3]], ptr[idx[6]], ptr[idx[7]]};! ! v8sf ff = __builtin_ia32_shufps256((v8sf)tbl_0145, (v8sf)tbl_2367, 0x88);! v8sf df = __builtin_ia32_shufps256((v8sf)tbl_0145, (v8sf)tbl_2367, 0xdd);! v8sf dr2 = r2 - (v8sf)r2_sl;! ff += dr2 * df;! ! v8sf mf = mj * ff;! ax += mf * dx; ay += mf * dy; az += mf * dz;!}!

配列初期化子任せ

shufpsで放送

粒子のプリロード

積和算 256-­‐bit整数命令 L1を経由

1次補間

Page 38: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

逆数平方根 ²  近似命令から収束公式だね!

³  Newton重力の計算を大幅に高速化 ²  2次収束を繰り返すだけが能じゃない

³  例:rsqrtpsは12-bit精度、53-bitの倍精度に近づけたければ ®  2次収束を3回 ®  単精度で2次収束をかけて倍精度で3次収束 ®  5次収束一発

³  積和算一回につき1次増やせる ®  係数のレジスタ圧迫は問題

³  任意のx-n/mに拡張できる ®  h = 1 ‒ xnym ®  y *= taylor[(1 ‒ h)-1/m]

Page 39: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

ハードのサポート状況 ²  3DNow! (忘れないでね)

³  pfrsqrt, pfrsqrti1, pfrsqrti2 ³  15-bitの近似と、収束補助命令

²  SSE/AVX ³  rsqrtps ³  12-bitの近似命令 ³  y *= (1.5f - (0.5f*x)*y*y)ないしy += y*(0.5f - (0.5f*x)*y*y)でほぼ単精度 ³  倍精度からだと型変換が煩わしい

²  Xeon Phi (KNC) ³  vrsqrt23ps ³  23-bit、なんと単精度なら生でいい ³  倍精度なら3次収束をかける

²  AVX-512 ³  vrsqrt{14|28}{ss|ps|sd|pd} ³  14-bitと、オプション(?)で28-bitになった ³  倍精度から直接呼べるというのが何より嬉しい、2次収束一発でいい

²  HPC-ACE (Sparc64, K computer extension) ³  倍精度2語に対して8-bit精度 ³  高級言語からはコンパイラが2次収束を3回吐く(組み込み関数で明示も可能)

Page 40: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

周期境界の補正 ²  周期境界では、絶対座標は0≤x<1、 相対座標は−0.5<Δx≤0.5のようにしたい

³  じゃあ整数で、という人もここには多いでしょうが、、、 ³  境界の開閉はこだわらないことにする

®  ここだけの話、倍精度から単精度の変換で絶対座標が1.0fになってしまうバグ出したことある

³  x -= round(x)みたく補正できばOK ®  call無し、分岐無し、ハードで SIMDでやりたいよね

®  roundpsとかなくても演算器の 後には丸めユニットがあります

double myround(double x){! // returns! // -1 for -1.5 < x <= -0.5! // 0 for -0.5 < x <= 0.5! // 1 for 0.5 < x <= 1.5! x += (double)(1 + (1LL << 52));! x -= (double)(1 + (1LL << 52));! return x;!}!

Page 41: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

Morton曲線とPeano-Hilbert曲線 ²  Octree構造と密接な関係

³  Morton/PH keyを作ってソートしておくツリーができたも同然

³  キャッシュヒットの改善、並列化のための領域分割にも使われる

³  2次元でのMorton ordering(左)と Hilbert ordering(右)

Page 42: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

Morton keyの実装 ²  x, y, zをそれぞれ21-bit整数にしたらビットシャッフルするだけ ³  ...cba(2) -> ...00c00b00a(2)

uint64 gen_morton(unsigned x, unsigned y, unsigned z){! uint64 key = 0;! for(int ish=20; ish>=0; ish--){! unsigned ix = (x>>ish)&1;! unsigned iy = (y>>ish)&1;! unsigned iz = (z>>ish)&1;! unsigned idx = 4*iz | 2*iy | ix;! key = (key<<3) | idx;! }! return key;!}

Then we can apply bit-based dilate primitives to compute the Morton key(List.1, [44]). This dilate primitive converts the first 10 bits of an integer toa 30 bit representation, i.e. 0100111011 ! 000 001 000 000 001 001 001

000 001 001:

List 1: The GPU code which we use to dilate the first 10-bits of an integer.

1 int dilate(const int value) {

2 unsigned int x;

3 x = value & 0x03FF;

4 x = ((x << 16) + x) & 0xFF0000FF;

5 x = ((x << 8) + x) & 0x0F00F00F;

6 x = ((x << 4) + x) & 0xC30C30C3;

7 x = ((x << 2) + x) & 0x49249249;

8 return x;

9 }

This dilate primitive is combined with bit-shift and OR operators to getthe particles’ key. In our implementation, we used 60-bit keys, which issu�cient for an octree with the maximal depth of 20 levels. We store a60-bit key in two 32-bit integers, each containing 30-bits of the key. Themaximal depth imposes a limit on the method, but so far we have never runinto problems with our simulations. This limitation can easily be lifted byeither going to 90-bit keys for 30 levels or by modifying the tree-constructionalgorithm when we reach deepest levels. This is something we are currentlyinvestigating.

35

こんな実装もあります

Page 43: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

Peano-Hilbertはもう少し難しい ²  3次元だと作り方が一意ではないのに注意 ²  自分の周囲に被害者数名

「素晴らしい本で,少しでもこのテーマに興味あるすべての人に全面的にお勧めである.」

•  これはみんなもってる?  •  2次元の場合の実装に関する  詳細な記述

•  読んだら3次元の実装ができる  とは限りません  

•  それでも数学好きにはお勧め

Page 44: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

先ずはGray code Incremental Gray  code

000! 000 001 001 010 011 011 010 100 110 101 111 110 101 111 100

zyx ^= zyx>>1;

zyx ^= (zyx>>1) ^ (zyx>>2);

•  一番上の階層はこれでいい  •  あとは部品の回転/反転

Page 45: Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)

namespace{! const uint64 xmask = 0111111111111111111111;! const uint64 ymask = 0222222222222222222222;! const uint64 zmask = 0444444444444444444444;!! inline uint64 swap_xy(uint64 key){! return ((key&xmask)<<1 | (key&ymask)>>1 | (key&zmask));! }! inline uint64 swap_yz(uint64 key){! return ((key&xmask) | (key&ymask)<<1 | (key&zmask)>>1);! }! inline uint64 swap_zx(uint64 key){! return ((key&xmask)<<2 | (key&ymask) | (key&zmask)>>2);! }! inline uint64 key_shuffle(uint64 key, int idx){! switch(idx/2 + idx%2){! case 4: // idx = 7! key ^= (xmask | zmask); // fall-through! case 0: // idx = 0! key = swap_zx(key);! break;! case 3: // idx = 5,6! key ^= (ymask | zmask); // fall-through! case 1: // idx = 1,2! key = swap_yz(key);! break;! case 2: // idx = 3,4! key ^= (xmask | ymask);! break;! }! return key;! }!}!

uint64 morton_to_ph(uint64 key){! uint64 ret = 0;! for(int ish=60; ish>=0; ish-=3){! unsigned idx = (key>>ish) & 7;! idx = (idx>>2) ^ (idx>>1) ^ (idx); // from Gray code! key = key_shuffle(key, idx);! ret = (ret<<3) | idx;! }! return ret;!}!

uint64 ph_to_morton(uint64 key){! uint64 tmp = key;! tmp ^= (tmp&(zmask|ymask)) >> 1; // to Gray code! for(int jsh=3; jsh<=60; jsh+=3){! unsigned idx = (key>>jsh) & 7;! uint64 sfl = key_shuffle(tmp, idx);! uint64 mask = ((uint64)1<<jsh) - 1;! tmp &= ~mask;! tmp |= sfl&mask;! }! return tmp;!}!

ご自由に最適化ください(終)