こんにちはモノトーンです。
今日は,Swift で SIMD 吐き出すための方法や,実際にどんなコードが吐き出されるか,そして最後には SIMDKit についてお話ししようと思います。
1. import simd で使える
CGFloat 型は 32-bit デバイスでは Float (Float32),64-bit デバイスでは Double (Float64)。
個人的には,32-bit デバイスの時点で,CGFloat は数値計算には使えませんので,正直,座標とか色とかに使うデータなら Float32 で統一して欲しいんですけどね……w 特に座標データに現時点で Float64 は別にいらないですし(まあ将来,32K ディスプレイとかになってきたら必要かもしれません)
まあそれはさておき,ARMv7 から使える Neon 命令は 128-bit まで扱えます。しかも,新しめのプロセッサーだと 128-bit 一回で演算すると思います。古めだと,64-bit 2回かな。
どちらにせよ,Intel 系プロセッサーのように 256-bit (AVX, AVX2) や 512-bit (AVX-512F) を演算することはできないですね。それでも,座標演算では,多少早くなることを期待できると思います。
あと,macOS 向けだと,AVX2 完全サポートしてる機種だけが対応しているわけじゃないので,ガチでやるなら,SSE 版と AVX 版作って分岐してパフォーマンスが出る設計にしないといけないですが,いちいちプラットフォーム検出とかこの手のライブラリーに組み込むのはめんどくさいので,SSE で処理させちゃうので,実質 NEON と変わりないコードが生成されるかと。
以下は単純なサンプルコードです。
import simd #if os(macOS) import struct CoreGraphics.CGGeometry.CGFloat import struct CoreGraphics.CGGeometry.CGRect #else import struct CoreGraphics.CGFloat import struct CoreGraphics.CGRect #endif extension CGRect { #if arch(arm64) || arch(x86_64) internal typealias SimdType = simd_double4 #else internal typealias SimdType = simd_float4 #endif internal init(simd: SimdType) { self.init(x: CGFloat(simd.x), y: CGFloat(simd.y), width: CGFloat(simd.z), height: CGFloat(simd.w)) } internal var simd: SimdType { return SimdType(x: self.origin.x.native, y: self.origin.y.native, z: self.size.width.native, w: self.size.height.native) } public static func + (lhs: CGRect, rhs: CGRect) -> CGRect { return CGRect(simd: lhs.simd + rhs.simd) } }
2. AArch64 の場合
double4 を fadd で 2 回演算してます。ARM は MIPS とかと同じく 3op 命令ですね。
dN は 64-bit レジスター (double word の頭文字 d),vN は 128-bit レジスター (vector の頭文字 v) です。
vN のインデックス [0] は [63:0] に対応,[1] は [127:64] に対応しています。
.globl _$SSo6CGRectV4mainE1poiyA2B_ABtFZ .p2align 2 _$SSo6CGRectV4mainE1poiyA2B_ABtFZ: mov.d v0[1], v1[0] ; v0[127:64] ← v1[63:0] # Ap = (Ax, Ay) mov.d v2[1], v3[0] ; v2[127:64] ← v3[63:0] # As = (Aw, Ah) mov.d v4[1], v5[0] ; v4[127:64] ← v5[63:0] # Bp = (Bx, By) mov.d v6[1], v7[0] ; v6[127:64] ← v7[63:0] # Bs = (Bw, Bh) fadd.2d v2, v2, v6 ; v2 ← v2 + v6 # Cs = As + Bs fadd.2d v0, v0, v4 ; v0 ← v0 + v4 # Cp = Ap + Bp mov d1, v0[1] ; d1 ← v0[127:64] # Cy をコピー mov d3, v2[1] ; d3 ← v2[127:64] # Ch をコピー ret
※ swiftc -emit-assembly -whole-module-optimization -Osize CGRect+Operators.swift -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.1.sdk -target arm64-apple-ios8.0
でコンパイル
入力
- d0: Ax
- d1: Ay
- d2: Aw
- d3: Ah
- d4: Bx
- d5: By
- d6: Bw
- d7: Bh
出力
- d0: Cx
- d1: Cy
- d2: Cw
- d3: Ch
3. AMD64 / Intel 64 の場合
AT&T Syntax です。
xmmN レジスターは 128-bit レジスターという意味です。
.globl _$SSo6CGRectV4mainE1poiyA2B_ABtFZ .p2align 4, 0x90 _$SSo6CGRectV4mainE1poiyA2B_ABtFZ: pushq %rbp movq %rsp, %rbp unpcklpd %xmm1, %xmm0 ; xmm0[127:0] ← (xmm1[63:0] << 64) | xmm0[63:0] # Ap = (Ax, Ay) unpcklpd %xmm3, %xmm2 ; xmm2[127:0] ← (xmm3[63:0] << 64) | xmm2[63:0] # As = (Aw, Ah) unpcklpd %xmm5, %xmm4 ; xmm4[127:0] ← (xmm5[63:0] << 64) | xmm4[63:0] # Bp = (Bx, By) addpd %xmm0, %xmm4 ; xmm4 ← xmm0 + xmm4 # Cp = Ap + Bp unpcklpd %xmm7, %xmm6 ; xmm6[127:0] ← (xmm7[63:0] << 64) | xmm6[63:0] # Bs = (Bw, Bh) addpd %xmm2, %xmm6 ; xmm6 ← xmm2 + xmm6 # Cs = As + Bs movapd %xmm4, %xmm1 ; xmm1 ← xmm4 # Cx movhlps %xmm1, %xmm1 ; xmm1 ← (xmm1[127:64] << 64) | xmm1[63:0] # Cy (下位 64-bit のみが使われる) movapd %xmm6, %xmm3 ; xmm3 ← xmm6 # Cw movhlps %xmm3, %xmm3 ; xmm3 ← (xmm3[127:64] << 64) | xmm3[63:0] # Ch (下位 64-bit のみが使われる) movapd %xmm4, %xmm0 ; xmm0 ← xmm4 # Cx をコピー movapd %xmm6, %xmm2 ; xmm2 ← xmm6 # Cw をコピー popq %rbp retq
※ swiftc -emit-assembly -whole-module-optimization -Osize CGRect+Operators.swift
でコンパイル
入力
- xmm0: Ax
- xmm1: Ay
- xmm2: Aw
- xmm3: Ah
- xmm4: Bx
- xmm5: By
- xmm6: Bw
- xmm7: Bh
出力
- xmm0: Cx
- xmm1: Cy
- xmm2: Cw
- xmm3: Ch
4. SIMDKit について
CGPoint
, CGSize
, CGVector
, UIEdgeInsets/NSEdgeInsets
, NSDirectionalEdgeInsets
(macOS 除く) の演算関連を定義しています。
4.1 CGPoint
, CGSize
, CGVector
系 float2/double2
- Functions
- Operators (V は Vector, C は Const.)
- Mutatings
- add(x:), add(y:), add(x:y:), add(offset:)
- Nonmutatings
- adding(x:), adding(y:), adding(x:y:), adding(offset:)
- setting(x:), setting(y:)
4.2 CGRect
系 float4/double4
- Functions
- Operators (V は Vector, C は Const.)
- -V
- V + V, C + V, V + C
- V - V, C - V, V - C
- V * V, C * V, V * C
- V / V, C / V, V / C
- V += V, V += C
- V -= V, V -= C
- V = V, V = C
- V /= V, V /= C
- Mutatings
- add(x:), add(y:), add(x:y:), add(offset:)
- add(width:), add(height:), add(width:height:), add(length:), add(size:)
- Nonmutatings
- adding(x:), adding(y:), adding(x:y:), adding(offset:)
- adding(width:), adding(height:), adding(width:height:), adding(length:), adding(size:)
- setting(x:), setting(y:), setting(x:y:), setting(origin:)
- setting(width:), setting(height:), setting(width:height:), setting(length:), setting(size:)
4.3 UIEdgeInsets
, NSEdgeInsets
系 float4/double4
- Functions
- Operators (V は Vector, C は Const.)
- -V
- V + V, C + V, V + C
- V - V, C - V, V - C
- V * V, C * V, V * C
- V / V, C / V, V / C
- V += V, V += C
- V -= V, V -= C
- V = V, V = C
- V /= V, V /= C
- Mutatings
- add(top:), add(left:), add(bottom:), add(right:), add(vertical:), add(horizontal:), add(all:)
- Nonmutatings
- adding(top:), adding(left:), adding(bottom:), adding(right:), adding(vertical:), adding(horizontal:), adding(all:)
- setting(top:), setting(left:), setting(bottom:), setting(right:), setting(vertical:), setting(horizontal:)
4.4 NSDirectionalEdgeInsets
系 float4/double4
- Functions
- Operators (V は Vector, C は Const.)
- -V
- V + V, C + V, V + C
- V - V, C - V, V - C
- V * V, C * V, V * C
- V / V, C / V, V / C
- V += V, V += C
- V -= V, V -= C
- V = V, V = C
- V /= V, V /= C
- Mutatings
- add(top:), add(leading:), add(bottom:), add(trailing:), add(vertical:), add(horizontal:), add(all:)
- Nonmutatings
- adding(top:), adding(leading:), adding(bottom:), adding(trailing:), adding(vertical:), adding(horizontal:), adding(all:)
- setting(top:), setting(leading:), setting(bottom:), setting(trailing:), setting(vertical:), setting(horizontal:)
まとめ
初めて Xcode でマルチプラットフォームプロジェクトを作ってみたんですけど,結構簡単に作れますね。あと,Carthage も tag さえつければ特に何もしなくていいのもすごくいいですねー。
これのサブセット版を今の開発アプリで作り始めて,それが手を広げすぎて,こんなことになっちゃいましたがw
ま,これだけ用意しておけば,色々困ることもないでしょう。
個人的に,上位から順番に反映させていく,UIEdgeInsets の setting(horizontal:) とか結構便利だなーって思ってますね。layoutMargins.setting(horizontal: 10.0) みたいに,左右の余白を 10.0 にして,上下はそのまま引き継ぐ,みたいな記述がとても楽なので。
ま,名前に SIMD って入れたんですけど,あんまりいいのが思いつかなかったのと,おそらく誰も使わないんで,テキトーにって感じですかねw
不具合あったら GitHub の Issue に投げてくださいー。