6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Common Lisp で型宣言を使ってゴリゴリに最適化しているときにハマったこと

Posted at

背景

表題の通り。
処理系は sbcl を使いました。

問題

こちらが問題のコードです。
(実際には大きなコードで、問題の核だけ切り出しました)

(defun a (i)
  (declare (optimize (speed 3) (space 0) (safety 2))
           (type fixnum i))
  (the fixnum (+ i 1)))

この関数をコンパイルすると以下のメッセージが得られます。

; note: doing signed word to integer coercion (cost 20), for:
;       the first result of inline (signed-byte 64) arithmetic

なんだか、(signed-byte 64) 型から integer 型に変換するのに 20 の cost がかかるよ、と言われている気がします。

このメッセージを消したいです。

考察

コードを見ると、まず、declare によって ifixnum 型だよ、と宣言されています。
そこで、(+ i 1) の型を考えます。
(+ i 1)fixnum に収まりません(i = most-positive-fixnum のときですね)。
そのため、コンパイラは (+ i 1) の型を、より表現力の大きい (signed-byte 64) と推論しました。
その値を the オペレータによって fixnum に強制しようとしているため、上記のメッセージが出たと考えられます。

disassemle すると

; disassembly for A
; Size: 46 bytes. Origin: #x22B0A82B
; 2B:       48FFC0           INC RAX                          ; no-arg-parsing entry point
; 2E:       48D1E0           SHL RAX, 1
; 31:       7108             JNO L0
; 33:       48D1D8           RCR RAX, 1
; 36:       E86565FFFE       CALL #x21B00DA0                  ; ALLOC-SIGNED-BIGNUM-IN-RAX
; 3B: L0:   A801             TEST AL, 1
; 3D:       7509             JNE L1
; 3F:       488BD0           MOV RDX, RAX
; 42:       488BE5           MOV RSP, RBP
; 45:       F8               CLC
; 46:       5D               POP RBP
; 47:       C3               RET
; 48: L1:   488BD0           MOV RDX, RAX
; 4B:       48D1F8           SAR RAX, 1
; 4E:       7304             JNB L2
; 50:       488B42F9         MOV RAX, [RDX-7]
; 54: L2:   CC47             BREAK 71                         ; OBJECT-NOT-FIXNUM-ERROR
; 56:       02               BYTE #X02                        ; RAX
; 57:       CC0F             BREAK 15                         ; Invalid argument count trap

確かにコストの大きそうな関数です。ただ1を足すだけの関数なのに。

fixnum にそんなに大きな値は求めていない

私のアプリケーションでは、imost-positive-fixnum (4611686018427387903)のような大きな値にはなることはなく、(+ i 1)fixnum の表現できる範囲を超える心配はありません(文字列を舐めるために使っている値なので)。
私のアプリケーションでなくとも、そんなに大きな値は扱わないよ!というケースは多いと思います。

安全性を捨てよう

結論からいうと、(safety 2)(safety 0) にすることで解決しました。

(defun b (i)
  (declare (optimize (speed 3) (space 0) (safety 0))
           (type fixnum i))
  (the fixnum (+ i 1)))

これでコンパイル時のメッセージは表示されなくなりました。

また、disassemble の結果は以下の通りです。

; disassembly for B
; Size: 13 bytes. Origin: #x22B0B929
; 29:       4883C002         ADD RAX, 2                       ; no-arg-parsing entry point
; 2D:       488BD0           MOV RDX, RAX
; 30:       488BE5           MOV RSP, RBP
; 33:       F8               CLC
; 34:       5D               POP RBP
; 35:       C3               RET

だいぶ早くなってそうです。

局所的に安全性を捨てよう

取り上げた関数の場合はとても小さいのでこれで問題ないのですが、関数全体を (safety 0) にするのはまずいという場合は locally オペレータを使いましょう。

(defun c (i)
  (declare (optimize (speed 3) (space 0) (safety 2))
           (type fixnum i))
  (locally (declare (optimize (safety 0)))
    (the fixnum (+ i 1))))

こんな感じですね。

ちなみに、捨て去られた安全性は…

(safety 2) の場合、 most-positive-fixnum を与えると、

The value
  4611686018427387904
is not of type
  FIXNUM
   [Condition of type TYPE-ERROR]

実行時型エラーとなりました。

(safety 0) の場合、

CL-USER> (b most-positive-fixnum)
-4611686018427387904

オーバーフローしてしまいました。

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?