この文章は、 Lisp Advent Calendar 2015 - Qiita の記事として書かれました。
以下のコードは、平方根を取る関数 SQRT を呼び出すだけの関数を定義しています。
(defun hoge (n) (declare (optimize (speed 3) (safety 0) (debug 0)) (type double-float n)) (sqrt n))
これをSBCLでコンパイルすると、「最適化し損なったよ」というメッセージが出ます。
; file: /private/var/tmp/tmp.Rs4pbf ; in: DEFUN HOGE ; (SQRT N) ; ; note: unable to ; optimize ; due to type uncertainty: ; The result is a (VALUES (OR (DOUBLE-FLOAT 0.0d0) (COMPLEX DOUBLE-FLOAT)) ; &OPTIONAL), not a (VALUES FLOAT &REST T). ; ; compilation unit finished ; printed 1 note
これは SQRT に渡された数が0以上だった場合は戻り値が double-float, 負だった場合は戻り値が複素数になるので、戻り値の型が定まらなくて最適化できないよ・・ということを言っています。
対処の一例として、以下のように型宣言の所で渡す数が0以上と宣言すれば、戻り値の型は double-float と定まるので最適化が進みます。(もちろん、何らかの方法で関数に渡される値が0以上であることを保証する必要があります)
(defun hoge2 (n) (declare (optimize (speed 3) (safety 0) (debug 0)) (type (double-float 0d0) n)) ; ここで範囲を指定する (sqrt n))
速度比較してみます。
変更前
CL-USER> (time
(loop with num = 1d0
repeat 10000000
do (hoge num) (incf num 1d0)))
Evaluation took:
0.213 seconds of real time
0.215856 seconds of total run time (0.196790 user, 0.019066 system)
[ Run times consist of 0.042 seconds GC time, and 0.174 seconds non-GC time. ]
101.41% CPU
489,432,347 processor cycles
319,981,504 bytes consed
NIL
変更後
CL-USER> (time
(loop with num = 1d0
repeat 10000000
do (hoge2 num) (incf num 1d0)))
Evaluation took:
0.192 seconds of real time
0.195025 seconds of total run time (0.176769 user, 0.018256 system)
[ Run times consist of 0.039 seconds GC time, and 0.157 seconds non-GC time. ]
101.56% CPU
441,576,247 processor cycles
319,995,104 bytes consed
NIL
1割ほど早くなりました。