Igo : sbcl-1.0.28, sbcl-1.0.37
以前、計時を行った条件に合わせてcommon lisp版のIgo(0.2.3)をsbclのバージョン1.0.28で動かしてみたところ、処理を終えるまでに92.712秒かかった。
sbclのバージョンを1.0.37に替え、それ以外は同様の条件で試してみたところ、こちらは33.962秒で終了。
3倍近く処理時間が違う。
何故こんなに差がでるのか簡単に調べてみたところ、原因の一つはwith-slotsマクロの最適化にあるらしいことが分かった。
以下、例。
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; code-stream.lispというファイル内のunread関数のdisassemble結果比較 ;; 関数定義 ;; with-slotsマクロを使っているということだけが重要 (defun unread (code-stream) (declare (code-stream code-stream)) (with-slots (position surrogate?) code-stream (if surrogate? (setf surrogate? nil) (decf position)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; sbcl-1.0.37でのdisassemble結果 ; disassembly for IGO.CODE-STREAM:UNREAD ; 0AB75B42: 8B420F MOV EAX, [EDX+15] ; no-arg-parsing entry point ; 45: 3D0B001001 CMP EAX, 17825803 ; 4A: 740F JEQ L1 ; 4C: B80B001001 MOV EAX, 17825803 ; 51: 89420F MOV [EDX+15], EAX ; 54: 8BD0 MOV EDX, EAX ; 56: L0: 8BE5 MOV ESP, EBP ; 58: F8 CLC ; 59: 5D POP EBP ; 5A: C3 RET ; 5B: L1: 8B420B MOV EAX, [EDX+11] ; 5E: 83E804 SUB EAX, 4 ; 61: 89420B MOV [EDX+11], EAX ; 64: 8BD0 MOV EDX, EAX ; 66: EBEE JMP L0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; sbcl-1.0.28でのdisassemble結果 ; disassembly for IGO.CODE-STREAM:UNREAD ; 0A9F6AC5: 8D5C24F8 LEA EBX, [ESP-8] ; no-arg-parsing entry point ; AC9: 83EC0C SUB ESP, 12 ; ACC: 8B55FC MOV EDX, [EBP-4] ; ACF: 8B05906A9F0A MOV EAX, [#xA9F6A90] ; #<FDEFINITION object for (SB-PCL::SLOT-ACCESSOR ; :GLOBAL ..)> ; AD5: B904000000 MOV ECX, 4 ; ADA: 892B MOV [EBX], EBP ; ADC: 8BEB MOV EBP, EBX ; ADE: FF5005 CALL DWORD PTR [EAX+5] ; AE1: 81FA0B001001 CMP EDX, 17825803 ; AE7: 742F JEQ L2 ; AE9: 8D5C24F8 LEA EBX, [ESP-8] ; AED: 83EC0C SUB ESP, 12 ; AF0: BA0B001001 MOV EDX, 17825803 ; AF5: 8B7DFC MOV EDI, [EBP-4] ; AF8: 8B05946A9F0A MOV EAX, [#xA9F6A94] ; #<FDEFINITION object for (SB-PCL::SLOT-ACCESSOR ; :GLOBAL ..)> ; AFE: B908000000 MOV ECX, 8 ; B03: 892B MOV [EBX], EBP ; B05: 8BEB MOV EBP, EBX ; B07: FF5005 CALL DWORD PTR [EAX+5] ; B0A: 7302 JNB L0 ; B0C: 8BE3 MOV ESP, EBX ; B0E: L0: BA0B001001 MOV EDX, 17825803 ; B13: L1: 8BE5 MOV ESP, EBP ; B15: F8 CLC ; B16: 5D POP EBP ; B17: C3 RET ; B18: L2: 8D5C24F8 LEA EBX, [ESP-8] ; B1C: 83EC0C SUB ESP, 12 ; B1F: 8B55FC MOV EDX, [EBP-4] ; B22: 8B05986A9F0A MOV EAX, [#xA9F6A98] ; #<FDEFINITION object for (SB-PCL::SLOT-ACCESSOR ; :GLOBAL ..)> ; B28: B904000000 MOV ECX, 4 ; B2D: 892B MOV [EBX], EBP ; B2F: 8BEB MOV EBP, EBX ; B31: FF5005 CALL DWORD PTR [EAX+5] ; B34: 83EA04 SUB EDX, 4 ; B37: 8BF2 MOV ESI, EDX ; B39: 8975F8 MOV [EBP-8], ESI ; B3C: 8BD6 MOV EDX, ESI ; B3E: 8D5C24F8 LEA EBX, [ESP-8] ; B42: 83EC0C SUB ESP, 12 ; B45: 8B7DFC MOV EDI, [EBP-4] ; B48: 8B059C6A9F0A MOV EAX, [#xA9F6A9C] ; #<FDEFINITION object for (SB-PCL::SLOT-ACCESSOR ; :GLOBAL ..)> ; B4E: B908000000 MOV ECX, 8 ; B53: 892B MOV [EBX], EBP ; B55: 8BEB MOV EBP, EBX ; B57: FF5005 CALL DWORD PTR [EAX+5] ; B5A: 7302 JNB L3 ; B5C: 8BE3 MOV ESP, EBX ; B5E: L3: 8B75F8 MOV ESI, [EBP-8] ; B61: 8BD6 MOV EDX, ESI ; B63: EBAE JMP L1
明らかにdisassembleの結果が異なっている。
1.0.37ではcode-streamのフィールド(スロット)に直接アクセスしているのに対して、1.0.28ではsb-pcl::slot-accessor関数を通してのアクセスとなっている。
3倍近く差が出るのも納得。
unread関数(及び-ここでは出てきていないが-read関数)の定義を以下のように修正したところ、所要時間は36.374秒にまで縮まった。
;; symbol-macroletを使うように修正 (defun unread (code-stream) (declare (code-stream code-stream)) (symbol-macrolet ((position (position code-stream)) (surrogate? (surrogate? code-stream))) (if surrogate? (setf surrogate? nil) (decf position)))) ;; 1.0.28でのdisassemble結果 ;; 1.0.37でwith-slotsを使った場合とほぼ同様 ; disassembly for IGO.CODE-STREAM:UNREAD ; 0B89D892: 8B420F MOV EAX, [EDX+15] ; no-arg-parsing entry point ; 95: 3D0B001001 CMP EAX, 17825803 ; 9A: 7510 JNE L1 ; 9C: 8B420B MOV EAX, [EDX+11] ; 9F: 83E804 SUB EAX, 4 ; A2: 89420B MOV [EDX+11], EAX ; A5: 8BD0 MOV EDX, EAX ; A7: L0: 8BE5 MOV ESP, EBP ; A9: F8 CLC ; AA: 5D POP EBP ; AB: C3 RET ; AC: L1: B80B001001 MOV EAX, 17825803 ; B1: 89420F MOV [EDX+15], EAX ; B4: 8BD0 MOV EDX, EAX ; B6: EBEF JMP L0
これでもまだ1.0.37との間には2秒ほどの差があるが、これはおそらくバージョンごとの基本性能の差。
1.0.34では1.0.28より1秒程度高速に、1.0.37では1.0.34よりさらに1秒程度高速になっていた。
バージョンが上がるごとに着実に速くなっていってるっぽいのが良い。