More Related Content Similar to Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ) Similar to Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ) (20) More from MITSUNARI Shigeo More from MITSUNARI Shigeo (20) Xeon PhiとN体計算コーディング x86/x64最適化勉強会6(@k_nitadoriさんの代理アップ)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 命令を使ってくれることもあるだろうが、得られる性能は
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版があります
³ 積和が多いと嬉しいね
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するわけにもいかな
いため
29. 同期が遅い!
² omp barrierで20μs、omp parallel {}で30μsぐらい
持っていかれる(実測)
³ Opteron 4 socket, 8 die, 64 coreのマシンより遅い
³ ステップあたり5回同期していたので納得できる
² 粒子ソートもHaswellの50倍ぐらい遅かった
³ 同期と同様馬鹿にならない時間がかかっていた
² Xeon Phiのボトルネック
³ 個々のコアが貧弱(これは仕方ないかもだけど)
³ コア間通信が遅い(キャッシュが分散しているため)
® ランタイムの改良で若干改善できるかも(同期の階層化)
² なんか、殆どGPUだよね(今日のPhiの話はここまで)
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),
として、
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
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;!
}!
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 }
こんな実装もあります
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;!
}!
ご自由に最適化ください(終)