SBCLの最適化能力はさすがA+といったところか「そこにいたのにいなかった」という返り値になる — #:g1

Posted 2011-10-30 05:22:00 GMT


SBCLでの最適化の話。
下記の2つのコード例はどちらもスタイル的にやってはいけないもので、最初のものは、deleteの返り値を使わず、副作用を期待する、二つ目は定数 (quote ((a . 1) (b . 2))) を破壊的に変更しています。
やってはいけないのは分かっているのですが、どうしてそういう結果になっているのか考えるのも面白いのでメモ。

deleteの副作用を期待する


(let ((u (list 'one 2 3 4)))
(push 0 u)
(delete 'one u)
u)
;=> (0 ONE 2 3 4)

(let ((u (list 'one 2 3 4)))
(push 0 u)
(delete 'one u :test #'equal)
u)
;=> (0 2 3 4)

上記2つのコードのどちらもコンパイルした際にdeleteの返り値を利用していないという警告がでます。
最初のdeleteは、:testを指定していないため、デフォルトの#'eqlが使われますが、
disassembleしてみるとこの場合、deleteの式自体が消えてしまっているようなので
結果もdeleteは実行されなかったのと同じになっています。
この結果の解釈ですが、deleteは副作用があるものという認識がありますが、効率の為に副作用があっても良いということでremoveと同じような働きをしていても問題はないということになります。
ということで、副作用を起こしておらず、返り値が使われていないremoveや、deleteは削除しても問題ないということになります。
(とはいえ、removeに書き換えてdisassembleするとremoveは残っており、deleteのように消えたりはしないようなのですが…。)
なんにしろdeleteは返り値が利用できるのみで、副作用は使えない、ということに変わりはありません。

定数を破壊する


(let ((alist '((a . 1) (b . 2))))
(setf (cdr (assoc 'a alist)) 10)
(setf (cdr (assoc 'b alist)) 20)

(list (cdr (assoc 'a alist))
(cdr (assoc 'b alist))))
;=> (1 2)

(let ((alist '((a . 1) (b . 2))))
(setf (cdr (assoc 'a alist)) 10)
(setf (cdr (assoc 'b alist)) 20)
alist)
;=> ((A . 10) (B . 20))

;; ちなみに
(let ((alist '((a . 1) (b . 2))))
(setf (cdr (assoc 'a alist)) 10)
(setf (cdr (assoc 'b alist)) 20)

(list (assoc 'a alist)
(assoc 'b alist)))
;=> ((A . 10) (B . 20))

こっちは、以前にchatonで質問してみたことがあって、コンパイラの最適化で(cdr (assoc 'a alist))も定数になると判断しているため結果に置き換わるのではないか、と教えてもらいました。
disassembleしてみると実際にそんな感じで、(cdr (assoc 'a alist))は1になってしまっており、一応内側で、#'(setf cdr) も実行されてるんですが、返り値のレジスタには既に展開された結果が乗ってしまっている、という感じでした。
ただ、この動作は、コンパイラでは良いと思うのですが、CLの場合、インタプリタでも同じように動作する筈で、その場合、この動きで良いのかなあという疑問があります。
…と考えていて、もしかして、CLの「コンパイラでもインタプリタでも同じ動作をする」というのは同じ処理系内で同一の動作をすることが求められているということで、
コンパイラしかない処理系の場合は、そもそもそんなことを気にする必要もなかったりするんだろうか、という「同じ動作」が処理系を跨ぐのかどうかという、新たな疑問が浮んできました。
とはいえ、あまり深追いする気もないので放置しておきます。
以上色々ありますが、なにはともあれ、定数は破壊してはいけないという原則に変わりはありません。

comments powered by Disqus