Emacs Lisp で quasiquote はどれくらい速いのか?

最近consとlistとかをあまり使わなくなったことに気付いた. 昔はリストを返すときは地道にconsしていた気がするのだけれど, いつからかquasiquoteの味をしめたようでツリーの場合はもちろん単純にconsやlistすればいいときなんかにも読み易さとか条件分岐の兼ね合いとかでquasiquoteを使っている場合も多い.

そういう事情もあってふと, 効率が気になった. コンパイラが頑張ればlistやconsを使うのと同等の測度が出るはずなので, 取り敢えず簡単な計測をしてみた.

(defun my:test-list (x)
  (list 0
        (list (list 1 2)
              (list x 4))))
(defun my:test-quasiquote (x)
  `(0 ((1 2) (,x 4))))

(my:test-list 100)
; => (0
;     ((1 2)
;      (100 4)))
(my:test-quasiquote 100)
; => (0
;     ((1 2)
;      (100 4)))

(let ((elp-function-list '(my:test-list my:test-quasiquote)))
  (elp-instrument-list)
  (dotimes (i 100000)
    (my:test-list 100)
    (my:test-quasiquote 100))
  (elp-results)
  (elp-reset-list))

実行すると以下の様な結果になった. (全て起動直後の値.)

(version)
; => "GNU Emacs 24.2.1 (amd64-portbld-freebsd9.1, GTK+ Version 2.24.6)

;;; Interpreted case
;;; 1st
;;  Function Name       Call Count  Elapsed Time  Average Time
;;  my:test-quasiquote  100000      2.0486769999  2.048...e-05
;;  my:test-list        100000      0.5728829999  5.728...e-06
;;; 2nd
;;  Function Name       Call Count  Elapsed Time  Average Time
;;  my:test-quasiquote  100000      2.0178639999  2.017...e-05
;;  my:test-list        100000      0.4008109999  4.008...e-06

;;; Compiled case
;;; 1st
;;  Function Name       Call Count  Elapsed Time  Average Time
;;  my:test-quasiquote  100000      0.3489519999  3.489...e-06
;;  my:test-list        100000      0.3064099999  3.064...e-06
;;; 2nd
;;  my:test-quasiquote  100000      0.1925460000  1.925...e-06
;;  my:test-list        100000      0.1898700000  1.898...e-06

まぁ予想通りですね. しかしコンパイルしてもquasiquoteの方がちょっと遅いのが何故なのか気になる.

(defun my:test-list-2 (x y)
  (list 0
        (list (list (cons x 2)
                    3)
              (list y
                    (list 5 6)))))
(defun my:test-quasiquote-2 (x y)
  `(0 (((,x . 2) 3) (,y (5 6)))))

;;; from quasiquote-test.elc
; (defalias 'my:test-list #[(x) "\301\302\303D^H\304DDD\207" [x 0 1 2 4] 4])
; (defalias 'my:test-quasiquote #[(x) "\301\302^H\303BDD\207" [x 0 (1 2) (4)] 4])
; (defalias 'my:test-list-2 #[(x y) "\302^H\303B\304D	\305\306DDDD\207" [x y 0 2 3 5 6] 5])
; (defalias 'my:test-quasiquote-2 #[(x y) "\302^H\303B\304B	\305BDD\207" [x y 0 2 (3) ((5 6))] 4])

コンパイルした .elc の中身はこんな感じになっていた. うーむ, 何でそこまでやってるのに最後までやらないんだ(笑)

取り敢えず速度に大きな影響を与えはしなさそうなので安心してquasiquoteを使うことにする.

`(Happy quasiquoted ,life !)


quasiquotedを使って構築されたリストを返すマクロ

蛇足. たまに, quasiquoteのレシピを返すマクロを書きたいときがある. (どうしてもコンパイル時に展開して欲しいんだけどmacroexpandがdefsubstを展開してくれないのでdefmacroを使うしかなかったり.)設計が悪いと言えばそうなのだけれど.

Emacs Lispではspecial formとして quasiquote や unquote は存在しない. 代りに \` と \, と \,@ を使う. (list 'unquote 'a) みたいなのを書くには `(,'\` 'a) と書く.

`(,x a)

みたいなのだと

`(,'\` ((,'\, x) a))

という風になる.

(defmacro f (y)
  `(,'\` ((,'\, ,y) a)))
(let ((x 1))
 (f x))
; => (1 a)

参考

Schemeのマクロ -- Island Life

>>私がLispを知るきっかけとなった『CプログラムブックIII Lisp処理系の作成』 でも、確か「熟練したLispプログラマはlistなんて使いません。 リストを作る時はbackquoteを使います」って説明があったような気がする。<<

quasiquote -- togetter
'`'と',' -- Qiita