Macro Forms as Places — #:g1

Posted 2017-01-17 13:50:39 GMT

(setf f)で標準で定義されている関数について確認していて、関数だけでなくマクロの(setf f)についても定義されているのに今更気付いた。

マクロ展開されてから、setfの展開が適用されると書いてあるが、マクロ定義が適切ならば、何もしなくてもsetfの定義もできているということらしい。

(macrolet ((nthcdr* (n list)
             `(cdr (etypecase ,n
                     ((eql 0) (cons nil ,list))
                     ((eql 1) ,list)
                     ((integer 2 *) (nthcdr (1- ,n) ,list))))))
  (let ((u (list 0 1 2 3)))
    (setf (nthcdr* 2 u) (list :x :y :z))
    u))(0 1 :x :y :z) 

なるほど確かにそうなっている。
ちなみに、試してみれば判ると思うが、setfのフォームでも適切に機能するように組むのはPlaceの要件を満さなくてはいけないので案外難しいと思う。

上記ではローカル定義だが、勿論大域でも問題ない

(defmacro nthcdr* (n list)
  `(cdr (etypecase ,n
          ((eql 0) (cons nil ,list))
          ((eql 1) ,list)
          ((integer 2 *) (nthcdr (1- ,n) ,list)))))

(macroexpand '(setf (nthcdr* 2 u) (list :x :y :z))) ==> (system::%rplacd (etypecase 2 ((eql 0) (cons nil u)) ((eql 1) u) ((integer 2 *) (nthcdr (1- 2) u))) (list :x :y :z)) t

エイリアスにマクロを使うのはコンパイラがインライン展開をしてくれなかった時代の悲しい習慣だと考えていたが一つメリットを発見してしまった。

とはいえ、コンパイラが自動でやってくれるものをあえてマクロを使うメリットはないだろう。
インライン展開をしてくれない処理系の為にそうする、という意見もあるが、インライン展開をしてくれない処理系は即ち、速度を出そうとしていない処理系なので、そこで頑張っても仕方ないのである。

一応速度比較をしてみるが、マクロでもインライン展開でも同じ速度になった。
(disassembleして出てくるコードもほぼ同じ)

(defun function-call-forms-as-place/flet ()
  (declare (optimize (speed 3) (debug 0) (safety 0)))
  (flet ((nthcdr* (n list)
           (nthcdr n list))
         ((setf nthcdr*) (val n list)
           (setf (cdr (etypecase n
                        ((eql 0) (cons nil list))
                        ((eql 1) list)
                        ((integer 2 *) (nthcdr (1- n) list))))
                 val)))
    (declare (inline nthcdr* (setf nthcdr*)))
    (let ((u (list 0 1)))
      (setf (nthcdr* 2 u) (list :x))
      (list u (nthcdr* 2 u)))))

(defun macro-forms-as-place () (declare (optimize (speed 3) (debug 0) (safety 0))) (macrolet ((nthcdr* (n list) `(cdr (etypecase ,n ((eql 0) (cons nil ,list)) ((eql 1) ,list) ((integer 2 *) (nthcdr (1- ,n) ,list)))))) (let ((u (list 0 1))) (setf (nthcdr* 2 u) (list :x)) (list u (nthcdr* 2 u)))))

(progn (time (dotimes (i 1000000))) (time (dotimes (i 1000000) (macro-forms-as-place))) (time (dotimes (i 1000000) (function-call-forms-as-place/flet))))

;;; LispWorks 7.0 x86_64 Linux ⊳ Timing the evaluation of (dotimes (i 1000000)) ⊳ ⊳ User time = 1.130 ⊳ System time = 0.000 ⊳ Elapsed time = 1.123 ⊳ Allocation = 1128023752 bytes ⊳ 0 Page faults ⊳ Calls to %EVAL 15000036 ⊳ Timing the evaluation of (dotimes (i 1000000) (macro-forms-as-place)) ⊳ ⊳ User time = 0.970 ⊳ System time = 0.000 ⊳ Elapsed time = 0.965 ⊳ Allocation = 1208018216 bytes ⊳ 0 Page faults ⊳ Calls to %EVAL 16000036 ⊳ Timing the evaluation of (dotimes (i 1000000)(function-call-forms-as-place/flet)) ⊳ ⊳ User time = 0.960 ⊳ System time = 0.000 ⊳ Elapsed time = 0.956 ⊳ Allocation = 1208025808 bytes ⊳ 0 Page faults ⊳ Calls to %EVAL 16000036

→ nil


HTML generated by 3bmd in LispWorks 7.0.0

comments powered by Disqus