Posted 2014-09-09 02:53:00 GMT
TL;TR 特に結論めいたものはありません。
SBCLを使っていると、check-typeを書くようなところをdeclare typeで済ませようという気になってしまいます。
declare typeでのチェックは型エラーにすることが保証されている訳ではないので、ちゃんと check-type を使いましょうというのは良く言われることではありますが、SBCLは、safetyを0にしたりしなければ、 check-type 的な動作をしてくれることもありdeclare typeを使ってしまう訳です。
そんな日々でしたが、Pythonコンパイラの論文であるThe Python Compiler for CMU Common Lispを眺めていたら、2 Type Checkingで、Pythonコンパイラでは、declare typeが強力なので check-type の代わりに積極的に活用していけますよ、的な調子で解説されていて、この中途半端な状況の大元はここだったかと思った最近です。
ちなみに型情報の伝播がdeclare typeの方がcheck-typeより良いようなことが書いてありますが、現在のSBCLでは、check-typeで書いた内容もdeclare typeと同じように伝播するようです(厳密には調べていないので、違っていたら教えてください)。
そんなcheck-typeとdeclare typeですが、何が違うのかといえばcheck-typeが値を再設定する再起動をするところです。つまり、その分遅くなるのですが、どんなものか調べてみました。
(defun foo (n) (declare (integer n) (optimize (safety 0) (speed 3) (debug 0))) n); disassembly for FOO (assembled 9 bytes) ; MOV RDX, RCX ; no-arg-parsing entry point ; MOV RSP, RBP ; CLC ; POP RBP ; RET
(defun foo-ct (n) (declare (optimize (safety 0) (speed 3) (debug 0))) (check-type n integer) n)
; disassembly for FOO-CT (assembled 99 bytes) ; JMP L1 ; no-arg-parsing entry point ; NOP ; NOP ;L0: MOV RDI, RDX ; LEA RBX, [RSP-16] ; SUB RSP, 32 ; MOV RDX, [RIP-131] ; 'N ; MOV RSI, [RIP-130] ; 'INTEGER ; MOV QWORD PTR [RBX-16], 537919511 ; MOV RAX, [RIP-137] ; #<FDEFINITION for SB-KERNEL:CHECK-TYPE-ERROR> ; MOV ECX, 8 ; MOV [RBX], RBP ; MOV RBP, RBX ; CALL QWORD PTR [RAX+9] ; CMOVB RSP, RBX ;L1: LEA EAX, [RDX-15] ; TEST AL, 1 ; JNE L2 ; TEST AL, 15 ; JNE L0 ; CMP BYTE PTR [RDX-15], 17 ; JNE L0 ;L2: MOV RSP, RBP ; CLC ; POP RBP ; RET
(loop :repeat (expt 10 9) :count (foo-ct 1)) ;=> 1000000000 #|------------------------------------------------------------| Evaluation took: 3.817 seconds of real time 3.852000 seconds of total run time (3.852000 user, 0.000000 system) 100.92% CPU 12,564,987,842 processor cycles 32,976 bytes consed
Intel(R) Xeon(R) CPU E3-1230 v3 @ 3.30GHz |------------------------------------------------------------|#
(loop :repeat (expt 10 9) :count (foo 1)) ;=> 1000000000 #|------------------------------------------------------------| Evaluation took: 3.286 seconds of real time 3.300000 seconds of total run time (3.300000 user, 0.000000 system) 100.43% CPU 10,819,408,209 processor cycles 65,632 bytes consed
Intel(R) Xeon(R) CPU E3-1230 v3 @ 3.30GHz |------------------------------------------------------------|#
大体15%位check-typeの方が遅いようですが、10億回回して、0.5秒の違い。チェックする変数が多ければ差はどんどん開くと思いますが、check-typeでも良いんじゃないかという気もしてきました。
チューニングの時に除去するのも手間でもない気がします(多分)が、policy-condみたいなものを使って、
(defmacro check-type* (place type &optional type-spec)
`(policy-cond:policy-if (eql 0 cl:safety)
(the ,type ,place)
(check-type ,place ,type ,type-spec)))
みたいなものを書いて使えば、SAFETY 0の時には再起動しないcheck-typeみたいなものもできるかなと思います。
ちなみに昔のコードには、両方書いてるような場合も結構ありますが、こんな感じに併用すると
(defun foo-decl&ct (n)
(declare (optimize (safety 0) (speed 3))
(integer n))
(check-type n integer)
n)
SBCLではdeclareの方が勝つようで、check-typeのコードは消えてなくなります。
ということで、最適化時にcheck-typeを迂回したければ、型宣言を追加するというのもSBCLでは可能ではあります(しかし、deleting unreachable codeの警告が出ます)。
check-typeの方がポータブルでCommon Lisp的には正しいコードだけど、declare typeも捨てがたい、しかし、check-typeで通しても良いんじゃないか、という感じのことを書いてみました。
とはいえ、速度はあまり気にならないところではcheck-typeだなとは思います。