y2q_actionman’s ゴミクズチラ裏

内向きのメモ書きを置いてます

細かすぎて使いどころがなさそうな Common Lisp の最適化ネタ

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