Hatena::Groupcadr

わだばLisperになる このページをアンテナに追加 RSSフィード

2004 | 12 |
2005 | 01 | 02 | 07 | 10 | 11 |
2006 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 11 |

2010-11-20

LAMBDAを使うなスタイル (1)

| 23:49 | LAMBDAを使うなスタイル (1) - わだばLisperになる を含むブックマーク はてなブックマーク - LAMBDAを使うなスタイル (1) - わだばLisperになる

先日のLISPセミナー参加者との雑談で「LAMBDAを使うな、LOOPを使え」というスタイルがあるということを知りました。

それだったら、高階スタイルで書いたフォームをSeriesみたいにマクロでループに展開してしまえば良いじょのいこ!、と思って家に帰ってちょっと書き始めたのですが、いやいや、その辺りは、コンパイラが良きに計らってくれるんじゃないか、と思い、SBCLではどうなのかを少し追い掛けてみました。

とりあえず、

(mapc (lambda (x) x) '(1 2 3 4))

のようなコードをお題に調べて行きます。

まず、MAPCの定義を眺めてみると、最初に、MAPCにはコンパイラにより最適化の変形が施されます。

これには、SB-C::MAPFOO-TRANSFORM(SB-Cの-CはコンパイラのC)が使われて、

(let ((list '(1 2 3 4))
      (more-lists () ))
  (sb-c::mapfoo-transform (lambda (x) x) (cons list more-lists) nil t))
;=> (LET ((#:G3164 (SB-KERNEL:%COERCE-CALLABLE-TO-FUN #)))
;     (LET ((#:G3162 (1 2 3 4)))
;       (SB-INT:DO-ANONYMOUS ((#:G3163 #:G3162 (CDR #:G3163)))
;                            ((OR (ENDP #:G3163)) (SB-EXT:TRULY-THE LIST #:G3162))
;                            (SB-C::%FUNCALL #:G3164 (CAR #:G3163)))))

のような感じで元の式から変形された式が取り出されます。

ここのDO-ANONYMOUSですが、DOのブロック名がNILではなくて、GENSYMなブロック名になっているもののようです。

ここでDO-ANONYMOUSを展開すると、

(LET ((G3162 arg))
  (BLOCK G3168
    (LET ((G3163 G3162))
      (TAGBODY (GO G3170)
         G3169 (TAGBODY (SB-C::%FUNCALL G3164 (CAR G3163)))
               (PSETQ G3163 (CDR G3163))
         G3170 (UNLESS (OR (ENDP G3163)) (GO G3169))
               (RETURN-FROM G3168 (PROGN (SB-EXT:TRULY-THE LIST G3162))))))))

となっているのが分かりますが、べたべたのGOTOループです。

次に、手書きで、上の様にGOTOループに展開したものと、素直に書いたものが、どう違ってくるかを確かめます。

(defun call-my-mapc (fn arg)
  (let ((fn (sb-kernel:%coerce-callable-to-fun fn))
        (list arg))
    (block block
      (let ((tail list))
        (tagbody (go L2)
              L1 (tagbody (sb-c::%funcall fn (car tail)))
                 (psetq tail (cdr tail))
              L2 (unless (or (endp tail)) (go L1))
                 (return-from block
                   (progn 
                     (sb-ext:truly-the list list))))))))
(defun call-mapc (fn arg)
  (mapc fn arg))

この二つをDISASSEMBLEして比べてみたところ、当たり前かもしれませんが、全く同じ内容になることを確認しました。(長いので結果は省略)

とりあえず、分かることは、良く使うところは処理系がやっぱり良きに計らってくれるんだなという感じです。

しかし、他にもループメインのスタイルとLAMBDAを使うスタイルとでは、処理効率を考えた場合に気をつける点が違ってくるようなので引き続き比較して書いていきたいと思います。

ゲスト



トラックバック - http://cadr.g.hatena.ne.jp/g000001/20101120