背景
表題の通り。
処理系は 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 によって i は fixnum 型だよ、と宣言されています。
そこで、(+ 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 にそんなに大きな値は求めていない
私のアプリケーションでは、i が most-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
オーバーフローしてしまいました。