http://qiita.com/guicho271828/items/a095b106deec61477597 の続き。
アンローリングしてもここが遅い。
; 780: L2: 498B5011 MOV RDX, [R8+17]
; 784: F30F10545A01 MOVSS XMM2, [RDX+RBX*2+1]
; 78A: F30F59D1 MULSS XMM2, XMM1
; 78E: 488B5011 MOV RDX, [RAX+17]
; 792: F30F105C4A01 MOVSS XMM3, [RDX+RCX*2+1]
; 798: F30F58DA ADDSS XMM3, XMM2
; 79C: 488B5011 MOV RDX, [RAX+17]
; 7A0: F30F115C4A01 MOVSS [RDX+RCX*2+1], XMM3
; 7A6: 488D5102 LEA RDX, [RCX+2] <<<<
; 7AA: 4C8D5302 LEA R10, [RBX+2] <<<<
; 7AE: 4D8B6811 MOV R13, [R8+17]
; 7B2: F3430F10545501 MOVSS XMM2, [R13+R10*2+1] <<<<
; 7B9: F30F59D1 MULSS XMM2, XMM1
; 7BD: 4C8B5011 MOV R10, [RAX+17]
; 7C1: F3410F105C5201 MOVSS XMM3, [R10+RDX*2+1] <<<<
; 7C8: F30F58DA ADDSS XMM3, XMM2
; 7CC: 4C8B5011 MOV R10, [RAX+17]
; 7D0: F3410F115C5201 MOVSS [R10+RDX*2+1], XMM3
; 7D7: 4883C704 ADD RDI, 4
; 7DB: 4883C104 ADD RCX, 4
; 7DF: 4883C304 ADD RBX, 4
(row-major-aref array (+ i 2))
の時に、 定数2 がオフセットとしてアセンブラに畳み込まれておらず、 LEAで明に計算されてしまっている(7A6)。
7B2は本当はこうなってほしい:
; 7B2: F3430F10545501 MOVSS XMM2, [R13+R10*2+5]
そこで、SBCLの内部にダイブしてアセンブラをチューニングすることに。SBCLは好みの関数が特定の型に当てはまる時、手で書いた最速アセンブラに変換する機能が付いている。これは使う側のコード側で指定するのではなくて
;;;; 実際ではない例 : 使いにくい
(...
;; 普通バージョンを
;(my-function 0.1 0.2)
;; アセンブリバージョンにいちいち手で置き換える
(my-function-in-super-fast-assembly/single-float/single-float 0.1 0.2)
...)
型がマッチした時には自動で置き換わる。マッチしない時は元の定義が使われる。
(define-VOP (my-function-in-super-fast-assembly/single-float/single-float)
(:translate my-function)
(arg-types single-float single-float)
...)
(...
(my-function 0.1 0.2) ; コンパイラが自動でVOPのほうを使う
...)
さて、別に自分でアセンブリを書きたくはないので、SBCLにすでにある内部関数を使いたい。どうやらそれが sb-kernel:data-vector-ref-with-offset
だ。
(define-vop (data-vector-ref-with-offset/simple-array-single-float-c)
(:note "inline array access")
(:translate data-vector-ref-with-offset)
(:policy :fast-safe)
(:args (object :scs (descriptor-reg)))
(:info index offset)
(:arg-types simple-array-single-float (:constant low-index)
(:constant (constant-displacement other-pointer-lowtag
4 vector-data-offset)))
(:results (value :scs (single-reg)))
(:result-types single-float)
(:generator 4
(inst movss value (make-ea-for-float-ref object index offset 4))))
しかし使うとコンパイル中にエラーになる。
(defun rm-gemm+static-size+unroll2-k (ma mb mc)
(declare (optimize (speed 3) (debug 0) (safety 0) (space 0)))
(declare (type (matrix 500 500) ma mb mc))
...
(dotimes (col (/ cols 2))
(sb-kernel:data-vector-set-with-offset mc mc-index 0
(+ (sb-kernel:data-vector-ref-with-offset mc mc-index 0)
(* cell (sb-kernel:data-vector-ref-with-offset mb mb-index 0))))
->
full call to SB-KERNEL:DATA-VECTOR-REF-WITH-OFFSET
This is probably a bug in SBCL itself. (Alternatively, SBCL ...
[Condition of type SB-INT:BUG]
Restarts:
0: [ABORT] Abort compilation.
1: [*ABORT] Return to SLIME's top level.
2: [ABORT] abort thread (#<THREAD "worker" RUNNING {10051DE033}>)
Backtrace:
0: (SB-INT:BUG "full call to ~S" SB-KERNEL:DATA-VECTOR-REF-WITH-OFFSET)
1: (SB-C::PONDER-FULL-CALL #<SB-C::COMBINATION :FUN #<SB-C::REF :LEAF #<SB-C::GLOBAL-VAR :%SOURCE-NAME SB-KERNEL:DATA-VECTOR-REF-WITH-OFFSET :TYPE #1=#<SB-KERNEL:FUN-TYPE #> :DEFINED-TYPE #1# :WHERE-FRO..
2: (SB-C::IR2-CONVERT-FULL-CALL #<SB-C::COMBINATION :FUN #<SB-C::REF :LEAF #<SB-C::GLOBAL-VAR :%SOURCE-NAME SB-KERNEL:DATA-VECTOR-REF-WITH-OFFSET :TYPE #1=#<SB-KERNEL:FUN-TYPE #> :DEFINED-TYPE #1# :WHER..
3: (SB-C::IR2-CONVERT-BLOCK #<SB-C::CBLOCK 11 :START c1 {1005211BF3}>)
4: (IR2-CONVERT #<COMPONENT :NAME RM-GEMM+STATIC-SIZE+UNROLL2-K {1005223F73}>)
これは、以下の理由に依る。
- 例のVOPは 多次元配列に対してはマッチしない。
- SB-KERNEL:DATA-VECTOR-REF-WITH-OFFSET は
always-transferrable
という fun-info bit がついている。これは、 VOPに変換されない full call としてコンパイルされるとエラーになることを示すビットである。 IR2 コンパイルの PONDER-FULL-CALL はこれをチェックしている。
解決策としては、 多次元配列を1次元配列に変換するラッパ関数 (sb-kernel:%array-data-vector array)
を用いる。この "変換" はコンパイル時のメタ情報が変わるだけなので、実行時のオーバヘッドはない。どっちにせよVOPにマッチして変換される。
結果、できたアンローリングは以下。
(defun rm-gemm+static-size+unroll2-k (ma mb mc)
(declare (optimize (speed 3) (debug 0) (safety 0) (space 0)))
(declare (type (matrix 500 500) ma mb mc))
(let ((rows (array-dimension ma 0))
(cols (array-dimension mb 1)))
(declare (type fixnum rows cols))
(dotimes (row rows)
(dotimes (k cols)
(let ((cell (aref ma row k))
(mb-index (array-row-major-index mb k 0))
(mc-index (array-row-major-index mc row 0)))
(declare (fixnum mb-index mc-index))
(dotimes (col (/ cols 2))
(sb-kernel:data-vector-set-with-offset
(sb-kernel:%array-data-vector mc) mc-index 0
(+ (sb-kernel:data-vector-ref-with-offset
(sb-kernel:%array-data-vector mc) mc-index 0)
(* cell (sb-kernel:data-vector-ref-with-offset
(sb-kernel:%array-data-vector mb) mb-index 0))))
(sb-kernel:data-vector-set-with-offset
(sb-kernel:%array-data-vector mc) mc-index 1
(+ (sb-kernel:data-vector-ref-with-offset
(sb-kernel:%array-data-vector mc) mc-index 1)
(* cell (sb-kernel:data-vector-ref-with-offset
(sb-kernel:%array-data-vector mb) mb-index 1))))))))
mc))
(benchmark (10 t)
;; Evaluation took:
;; 2.262 seconds of real time
;; 2.260000 seconds of total run time (2.260000 user, 0.000000 system)
;; 99.91% CPU
;; 6,785,072,819 processor cycles
;; 32,784 bytes consed
(rm-gemm+static-size+unroll2-k *ma* *mb* *mc*))
(benchmark (10 t)
;; Evaluation took:
;; 2.696 seconds of real time
;; 2.696000 seconds of total run time (2.696000 user, 0.000000 system)
;; 100.00% CPU
;; 8,088,876,210 processor cycles
;; 33,056 bytes consed
(rm-gemm+static-size-k *ma* *mb* *mc*))
結構早くなった。以下ディスアセンブル。
; disassembly for RM-GEMM+STATIC-SIZE+UNROLL2-K
; Size: 208 bytes. Origin: #x1005962C88
; C88: 31FF XOR EDI, EDI ; no-arg-parsing entry point
; C8A: E9B3000000 JMP L5
; C8F: 90 NOP
; C90: L0: 31C9 XOR ECX, ECX
; C92: E99A000000 JMP L4
; C97: 660F1F840000000000 NOP
; CA0: L1: 4869C7F4010000 IMUL RAX, RDI, 500
; CA7: 4801C8 ADD RAX, RCX
; CAA: 498B5511 MOV RDX, [R13+17]
; CAE: F30F104C4201 MOVSS XMM1, [RDX+RAX*2+1]
; CB4: 4C69C1F4010000 IMUL R8, RCX, 500
; CBB: 4869C7F4010000 IMUL RAX, RDI, 500
; CC2: 31F6 XOR ESI, ESI
; CC4: EB5E JMP L3
; CC6: 660F1F840000000000 NOP
; CCF: 90 NOP
; CD0: L2: 488B5311 MOV RDX, [RBX+17]
; CD4: 4C8B5311 MOV R10, [RBX+17]
; CD8: F3410F105C4201 MOVSS XMM3, [R10+RAX*2+1]
; CDF: 4D8B5111 MOV R10, [R9+17]
; CE3: F3430F10544201 MOVSS XMM2, [R10+R8*2+1]
; CEA: F30F59D1 MULSS XMM2, XMM1
; CEE: F30F58D3 ADDSS XMM2, XMM3
; CF2: F30F11544201 MOVSS [RDX+RAX*2+1], XMM2
; CF8: 488B5311 MOV RDX, [RBX+17]
; CFC: 4C8B5311 MOV R10, [RBX+17]
; D00: F3410F105C4205 MOVSS XMM3, [R10+RAX*2+5] <<< Good!
; D07: 4D8B5111 MOV R10, [R9+17]
; D0B: F3430F10544205 MOVSS XMM2, [R10+R8*2+5] <<< Good!
; D12: F30F59D1 MULSS XMM2, XMM1
; D16: F30F58D3 ADDSS XMM2, XMM3
; D1A: F30F11544205 MOVSS [RDX+RAX*2+5], XMM2 <<< Good!
; D20: 4883C602 ADD RSI, 2
; D24: L3: 4881FEF4010000 CMP RSI, 500
; D2B: 7CA3 JL L2
; D2D: 4883C102 ADD RCX, 2
; D31: L4: 4881F9E8030000 CMP RCX, 1000
; D38: 0F8C62FFFFFF JL L1
; D3E: 4883C702 ADD RDI, 2
; D42: L5: 4881FFE8030000 CMP RDI, 1000
; D49: 0F8C41FFFFFF JL L0
; D4F: 488BD3 MOV RDX, RBX
; D52: 488BE5 MOV RSP, RBP
; D55: F8 CLC
; D56: 5D POP RBP
; D57: C3 RET
自動アンローリングは次回。