この文章は、 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割ほど早くなりました。