Your SlideShare is downloading. ×
0
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

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

8,794

Published on

Published in: Technology
0 Comments
12 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
8,794
On Slideshare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
49
Comments
0
Likes
12
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Xeon PhiとN体計算コーディング x86/x64最適化勉強会6 理化学研究所計算科学研究機構(@神戸) 似鳥啓吾(にたどりけいご)@k_nitadori
  • 2. 簡単に自己紹介 ²  神戸の京コンの建物でポスドクしています ³  09年3月、東大理学天文で博士号(一応天体物理学者) ³  指導教員はGRAPEで有名な牧野淳一郎 ³  ポスドク5年目(3つ目) ²  専門(自己申告):N体計算の高速化、並列化、 アルゴリズム改良、良い実装を作る ³  SSE/AVX/CUDA/MPI/OpenMPあたりはカバー ³  最近はXeon phiやHaswellなど ³  アセンブラの知識:組み込み関数でSIMD書いたり コンパイラの吐いたasmを眺める程度 ³  「京」向けのチューンも
  • 3. ゴードン・ベル賞に関して ²  スパコン上でのアプリケーションに対する論文賞 ³  ベンチマークランキング(TOP500, HPCC)ではない ³  冬にはスパコンを確保し計算を走らせたい ³  春に12ページぐらいの論文を投稿 ³  夏にはfinalist/不採択の結果通知がくる ³  秋のSC学会で口頭発表、それから受賞論文の発表 ²  似鳥が入った論文の成績 ³  09年:長崎のGeForceクラスタで価格性能部門賞、 10年には同部門佳作賞 (Honorable Mansion) ³  12年:「京」で受賞(部門なしの単独受賞) ³  06、07、08、13年には落選
  • 4. 今日話す内容 ²  どうやって40∼60分も間を持たせよう、、、 ³  適当に割り込み、つっこみお願いします ²  専門のN体計算のお話 ³  高次積分法で高精度に計算するお話 ®  Xeon Phi Nativeで実装したよ ³  テーブル参照で任意のforce shapeを実現するお話 ®  SSE/AVXでテーブル参照を頑張った ²  その際の若干トリッキーな最適化の話題 ³  普段のセミナーで「そんな話嬉しそうに延々とされても 困ります」といわれるような話題をいくつか ³  Xeon Phi (KNC)とAVX-512の命令セットの概観
  • 5. N体計算の支配方程式 ²  式はひとつ(話題は無数) ²  減算3回、積和6回、乗算3回、逆数平方根1回 ²  i-loopとj-loopの二重ループ、O(N2) ²  j-粒子がi-粒子を引っ張る ²  i-並列とj-並列のふたつの並列度 ³  i-並列ではj-粒子の放送によってメモリ帯域を節約 ³  j-並列では最後に部分力の総和演算が必要 ないし(発散を避けるために)  
  • 6. コードのほうが分 り易いという人用 ²  流用資料につき混合精度 ²  外側ループのomp parallel化は自明に近い と思う ²  レジスタが余っていたら 外側ループをアンロール しよう ²  j-粒子がキャッシュに収 まるようなブロック化/ 並列化も有効 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 命令を使ってくれることもあるだろうが、得られる性能は
  • 7. 高次の方法(4次のHermite積分法) ²  加速度の一階微分(jerk)も直接計算 ³  コスト:「倍にはならない」 ²  補間多項式を積分 ³  Hermite補間 ³  積分の始点と終点で、実質4点分の情報 ²  予測子修正子法 ³  未来の座標はテイラー展開で予測 ³  未来の加速度とjerkを予測子から計算 ³  補間多項式から修正子を構築 ³  反復してもいいけどしなくても4次精度 ®  Runge-Kuttaより高効率 t f Δv i+1i もっと高階微分も計算できる?(できます)
  • 8. 高階微分の計算 ²  2階微分(snap)まで使って6次精度 ²  3階微分(crackle)まで使って8次精度 ³  次の微分にはpopと名前が付いている ²  高次のものほど ³  レジスタ消費は多い ³  積和比率が高い(逆数平方根は一回) ³  同期オーバーヘッドが相対的に小さい 38  ops 60  ops 97  ops 144  ops Acceleration: Jerk: Snap: Crackle: .2.2 Direct calculation of higher order derivatives The gravitational acceleration from a particle j on a particle i and its first three me derivatives are expressed as Aij = mj rij r3 ij , (2.1) Jij = mj vij r3 ij − 3αAij, (2.2) Sij = mj aij r3 ij − 6αJij − 3βAij, (2.3) Cij = mj jij r3 ij − 9αSij − 9βJij − 3γAij. (2.4) Here, we call the first four time derivatives of the acceleration jerk, snap, crackle and op, and α, β and γ are given by α = rij · vij r2 ij , (2.5) β = |vij|2 + rij · aij r2 ij + α2 , (2.6) γ = 3vij · aij + rij · jij 2 + α(3β − 4α2 ), (2.7) al acceleration from a particle j on a particle i and its first three re expressed as Aij = mj rij r3 ij , (2.1) Jij = mj vij r3 ij − 3αAij, (2.2) Sij = mj aij r3 ij − 6αJij − 3βAij, (2.3) Cij = mj jij r3 ij − 9αSij − 9βJij − 3γAij. (2.4) first four time derivatives of the acceleration jerk, snap, crackle and γ are given by α = rij · vij r2 ij , (2.5) β = |vij|2 + rij · aij r2 ij + α2 , (2.6) γ = 3vij · aij + rij · jij r2 ij + α(3β − 4α2 ), (2.7) i and mi are the position, velocity, total acceleration, total jerk and Aij = mj rij r3 ij , (2.1) Jij = mj vij r3 ij − 3αAij, (2.2) Sij = mj aij r3 ij − 6αJij − 3βAij, (2.3) Cij = mj jij r3 ij − 9αSij − 9βJij − 3γAij. (2.4) rst four time derivatives of the acceleration jerk, snap, crackle and γ are given by α = rij · vij r2 ij , (2.5) β = |vij|2 + rij · aij r2 ij + α2 , (2.6) γ = 3vij · aij + rij · jij r2 ij + α(3β − 4α2 ), (2.7) and mi are the position, velocity, total acceleration, total jerk and and rij = rj − ri, vij = vj − vi, aij = aj − ai and jij = jj − ji divとsqrtをそれぞれ   10演算と数えてある
  • 9. 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版があります ³  積和が多いと嬉しいね
  • 10. 独立時間刻み法 ²  重力のN体計算では近接遭遇が多発 ³  いちいち全部の粒子の時間刻みを最小のものに切り 揃えていては無駄が多い ³  粒子ごとにadaptiveに刻み幅を下げることを考える ®  とりあえず2の冪に ®  他の粒子の座標には「予測子」を使う 1/1   1/2   1/4   1/8   •  同時にNact個のactive粒子をアップデート •  全N粒子の予測子を計算:O(N) •  Nact個の粒子の加速度を計算:O(NactN) •  力を積分して修正子:O(Nact) •  <Nact>∼N2/3
  • 11. 続き(ちょっと物理数学) ²  単位箱にN個の粒子をランダムに置いたときの平均粒子間距 離はN -1/3 ³  これはまあいいでしょう ²  そのときの最小の粒子ペア距離の期待値はN -2/3 ³  これを示すのは大変(誰かhelp!) ²  結果共有時間刻みではN個の粒子を同時に積分できたのが、 独立時間刻みではN2/3個に ³  時間刻みは最近接粒子との距離に比例すると仮定 ³  N=1000に対し<Nact>=50ぐらい(系の中心集中度や積分次数 にも依ります) ³  大幅に高速化したのはいいがi-粒子の並列度がそのまま食われて しまった
  • 12. 時間刻みの決め方 ²  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;! }
  • 13. 1ステップの手続き 1.  積分する粒子を選ぶ:O(Nact) or O(logN) (serial) 2.  全粒子の予測子を計算:O(N) (parallel) 3.  重力を計算:O(NactN) (parallel) 4.  修正子と新しい時間刻みを計算:O(Nact) (parallel) 5.  粒子を時間刻み順にソート:O(NactlogNact) (serial)
  • 14. そろそろ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+独自拡張)
  • 15. AVX-512の概観(ぱっと見) ²  次世代KNL (Knights Landing)から ³  普通のXeon/Core iにも搭載されるのだろうか? ²  XMM/YMMが復活、ZMMと共に32本 ³  マスクレジスタ、swizzle、bcastもサポート ²  EVEX prefix ³  62hからの4-byte ²  KNC(現行のPhi)のベクトル命令をベースにSSE/ AVXと下位互換を取るように整理したもの、といった 感じ ³  数学関数の取り扱いが若干変わった
  • 16. プログラミングモデル ²  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的な使い方(生産性は高いとの主張) ³  似鳥は試したことないのでよくわかりません、 講習会とかもやってるっぽい
  • 17. ニーモニック ²  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されていること
  • 18. 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 ³  速度はともかく、命令が存在する ³  コストは触ったライン数に依存とのこと
  • 19. 積和命令 ²  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の数が最小になるといいですね
  • 20. 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
  • 21. 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
  • 22. 応用: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本消費 ³  もっといい方法あったら教えてください
  • 23. 用途 ²  インテルさんは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で放送
  • 24. コードはこんな感じ ²  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するわけにもいかな いため
  • 25. 60コア240スレッドの使い方 ²  単純i-並列/j-並列で使うには多すぎる ³  今回の実装では、コア内4スレッドをi-並列に 60コアをj-並列にしてみた ®  環境変数KMP_AFFINITY=compactとしておくと、コ ア内4スレッドが連番に ®  OpenMPでは0から239のスレッド番号が取得できる ®  j-粒子が分割キャッシュに乗るという目論見 ®  affinityを切ると若干の性能低下 ®  部分力[Nact][60]はメモリに書き出して後から総和を 取る
  • 26. 性能 ²  コードは倍精度の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(積和化された)
  • 27. Gflops値 ²  粒子数の多いところ ではHaswell16コア 分ぐらい ²  GRAPEやGPUのよ うな振る舞い ²  CPUとの転送は存 在しないのだが、、、
  • 28. ステップあたりのμ秒 ²  200μ秒付近に壁 がある ²  同期オーバーヘッド のようだ
  • 29. 同期が遅い! ²  omp barrierで20μs、omp parallel {}で30μsぐらい 持っていかれる(実測) ³  Opteron 4 socket, 8 die, 64 coreのマシンより遅い ³  ステップあたり5回同期していたので納得できる ²  粒子ソートもHaswellの50倍ぐらい遅かった ³  同期と同様馬鹿にならない時間がかかっていた ²  Xeon Phiのボトルネック ³  個々のコアが貧弱(これは仕方ないかもだけど) ³  コア間通信が遅い(キャッシュが分散しているため) ®  ランタイムの改良で若干改善できるかも(同期の階層化) ²  なんか、殆どGPUだよね(今日のPhiの話はここまで)
  • 30. カットオフ関数がある計算 ²  宇宙論的なN体計算だと ³  遠方から(低周波)の重力は一様メッシュとFFTで ³  近距離(高周波)の重力は粒子から直接計算する方 法がよく使われる ®  このとき到達距離が数メッシュ間隔ぐらいのカットオフ関数 が掛かる ²  SSE/AVXで無理矢理テーブル参照で実装 ²  「京」でのGB賞ではこの関数を直接計算した
  • 31. 具体的なかたち ²  教科書にあったもの ²  分岐を排除したもの ³  「京」ではこれを直接計算した なく、S2 分布に対する厳密解であることには注意。 P3 M 法の PP パート(つまりは S2 soften された PM force の Newton 重力からの残 差)での重力相互作用へのカットオフ関数  ai = j=i mj(rj − ri) |rj − ri|3 gP3M(|rj − ri|)/η), (3) として表現する場合は、  gP3M(R) =    1 − 1 140 ` 224R3 − 224R5 + 70R6 + 48R7 − 21R8 ´ (0 ≤ R ≤ 1) 1 − 1 140 ` 12 − 224R2 + 869R3 − 840R4 + 224R5 + 70R6 − 48R7 + 7R8 ´ (1 ≤ R ≤ 2) 0 (2 ≤ R) , (4) とあらわせる。 2 φ(R) =   1 140 ˆ 208 − 112R2 + 56R4 − 14R5 − 8R6 + 3R7 ˜ (0 ≤ R ≤ 1) 1 140 » 12 R + 128 + 224R − 448R2 + 280R3 − 56R4 − 14R5 + 8R6 − R7 – (1 ≤ R ≤ 2) 1 R (2 ≤ R) . (5) 3 Optimization このままでは演算量も多く分岐もあるため、PP 相互作用の度にこれを計算するのはさ すがに無視できないコストとなる。そこでモダンな汎用計算機のための効率的な式変形を 試みてみよう。 S ≡ max(0, R − 1), (6) として、 gP3M(R) = 1 + R3 − 8 5 + R2 8 5 + R − 1 2 + R − 12 35 + R 3 20 − S6 3 35 + R 18 35 + R 1 5 (0 ≤ R ≤ 2) (7) ポテンシャルも写経: φ(R) =    1 140 ˆ 208 − 112R2 + 56R4 − 14R5 − 8R6 + 3R7 ˜ 1 140 » 12 R + 128 + 224R − 448R2 + 280R3 − 56R4 − 14R5 1 R 3 Optimization このままでは演算量も多く分岐もあるため、PP 相互作用の度に すがに無視できないコストとなる。そこでモダンな汎用計算機のた 試みてみよう。 S ≡ max(0, R − 1), として、
  • 32. 一応図示 ²  赤がポテンシャルの カットオフ ²  緑が今回欲しい力の カットオフ ²  残り2本は多重極展開 するとき使う物 ³  float4のtexfetch1Dで 一気にやろうとか考え てた
  • 33. テーブル参照で関数を評価 ²  普通に考えれば(int)(scale * r)でindexを 生成して適当な次数で補間してf(r)を計算 ²  しかし、r2 → f(r)/r3 を一発で評価するのが 最速に思える ³  サンプル間隔も最適に近い物を選びたい ³  何をindexに使うか、どうやって得るか? ³  若干トリッキーな方法をとった
  • 34. indexの生成 ²  座標は適当にスケールしておく ²  s = 2.0f + r2を計算 ²  指数部の下位4-bitと仮数部の上位6-bitを用いる ³  17-bit右シフトだけでいい ³  あとは1次補間で必要な精度に smax À 2 smax À 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 same exponent bits are aligned uniformly in linear scale unless the frac- tion bit is small. As an example, we illustrate how the sampling points of the look-up table depend on the pre-defined integers E and F in Fig. 4. We first see the cases in which either of E and F is zero, in Table 4 s-values, their exponent and fraction bits in the IEEE754 expressions, and their indices in the table for r ¼ 0, rcut=2 and rcut in the case of E ¼ 4 and F ¼ 6 (underlined portion of exponent and fraction bits). r s Exponent bits Fraction bits Index 0 2 (smin) 10000000 00000000000000000000000 0 rcut=2 3:2514 Â 104 10001101 11111100000001100000000 895 rcut 1:3005 Â 105 10001111 11111100000000000000000 1023 (smax) 10 -2 10 -1 10 0 10 1 10 2 10 3 10 4 f(r)rcut 3 /r Conventional 10 -2 10 -1 10 0 10 1 10 2 10 3 10 4 10 -3 10 -2 10 -1 10 0 f(r)rcut 3 /r r / rcut Presented A. Tanikawa et al
  • 35. AVXでのテーブル参照 ²  GatherはAVX2から(しかも遅い) ²  テーブルの実体は補間用の係数を含めてfloat table[1024][2]; ²  indexはpextrwないしL1経由で汎用レジス タに転送 ²  gatherの代わりに(movlps, movhps)^2, vinsertf128 ²  あとは適当にシャッフル
  • 36. 適当にシャッフル d5 f5 d4 f4 d1 f1 d0 f0 d7 f7 d6 f6 d3 f3 d2 f2 f0f1f2f3f4f5f6f7 d0d1d2d3d4d5d6d7 vshufps 0x88 vshufps 0xdd 0x88 = 2020(4), 0xdd = 3131(4)
  • 37. 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次補間
  • 38. 逆数平方根 ²  近似命令から収束公式だね! ³  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]
  • 39. ハードのサポート状況 ²  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回吐く(組み込み関数で明示も可能)
  • 40. 周期境界の補正 ²  周期境界では、絶対座標は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;! }!
  • 41. Morton曲線とPeano-Hilbert曲線 ²  Octree構造と密接な関係 ³  Morton/PH keyを作ってソートしておくツリーがで きたも同然 ³  キャッシュヒットの改善、並列化のための領域分割に も使われる ³  2次元でのMorton ordering(左)と Hilbert ordering(右)
  • 42. 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 to a 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 } こんな実装もあります
  • 43. Peano-Hilbertはもう少し難しい ²  3次元だと作り方が一意ではないのに注意 ²  自分の周囲に被害者数名 「素晴らしい本で,少し でもこのテーマに興味あ るすべての人に全面的に お勧めである.」 •  これはみんなもってる?   •  2次元の場合の実装に関する   詳細な記述 •  読んだら3次元の実装ができる   とは限りません   •  それでも数学好きにはお勧め
  • 44. 先ずは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); •  一番上の階層はこれでいい   •  あとは部品の回転/反転
  • 45. 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;! }! ご自由に最適化ください(終)

×