2016-01-10-01-便利だねコンパイラマクロ - project-enigma

2016-01-10-01-便利だねコンパイラマクロ

>> Site top >> weblog >> 月別アーカイブ >> 2016年01月のlog >> 2016-01-10-01-便利だねコンパイラマクロ

最終更新日付:2016/01/10 23:30:00


便利だねコンパイラマクロ

2016 年 01 月 10 日

数日前に少し書いた、iostream ライブラリの Common Lisp 移植(っぽい)ライブラリである CL-IOSTREAM、今回はやっぱり気になるあの話だ。

基本的な話として、CL-IOSTREAM のストリーム入出力は総称関数ベースで行なっている。出力用は operator_<< を定義することになる。defgeneric は以下のような感じ。

(defgeneric operator_<< (stream arg))

 

これは2項演算子で、streamarg を出力し、stream を返すことが求められている。CL-IOSTREAM では、基本的なデータ型 ── string とか integer とか character とか ── に対してデフォルトの実装を用意している(あるいはする予定)。

ところで、この operator_<< は実際にクライアントコードが利用するオペレータ _<< とは違うモノだ。_<< は以前書いたように、必要な値やマニピュレータを好きなだけ繋げることができる。

(_<< cout showpos (setw 4) a " * "
                  (setw 4) b " = "
                  (setw 7) (* a b) endl)

 

では _<< はどのようなモノか。これは関数で、以下のように定義されている。

(defun _<< (stream &rest args)
  (labels ((imp (stream lst)
             (if (null lst)
                 stream
                 (imp (operator_<< stream (car lst)) (cdr lst)))))
    (imp stream args)))

 

おやおや、末尾再帰を使っているから(多分)最適化してもらえると期待できるとはいえ、結局のところ繰り返し総称関数をコールしているわけだな。これは率直に言って遅い。コンパイル時点で利用できる情報はやはりあるわけで、それは活用すべきだ。

こういうときはコンパイラマクロの出番だ。パラメータのうち、文字列や数値、文字などの即値が与えられている場合、総称関数ではなくその向こうにある実装関数を直接コールするようなものに展開できる。あと、既知のマニピュレータの場合も同じことができる。長いので、途中の部分を省略したモノを以下に示そう。

(define-compiler-macro _<< (stream &rest args)
  (labels ((imp (args acc)
             (if (null args)
                 acc
                 (let ((arg  (car args))
                       (rest (cdr args)))
                   (if (and (listp arg) (= 2 (length arg)))
                       (case (car arg)
                         ((resetiosflags) (imp rest `(__ios_resetiosflags ,acc ,(cadr arg))))
                         ;;  : omit
                         ((setw)          (imp rest `(__ios_setw          ,acc ,(cadr arg))))
                         (t               (imp rest `(operator_<<         ,acc ,arg))))
                       (case arg
                         ((endl)         (imp rest `(__os_endl         ,acc)))
                         ((flush)        (imp rest `(__os_flush        ,acc)))
                         ;;  : omit
                         ((hexfloat)     (imp rest `(__os_hexfloat     ,acc)))
                         ((defaultfloat) (imp rest `(__os_defaultfloat ,acc)))
                         (t
                          (typecase arg
                            (string    (imp rest `(__write-string     ,acc ,arg)))
                            (character (imp rest `(__write-character  ,acc ,arg)))
                            (integer   (imp rest `(__write-integer    ,acc ,arg)))
                            (number    (imp rest `(__write-number     ,acc ,arg)))
                            (symbol    (imp rest `(operator_<<        ,acc ,arg)))
                            (t         (imp rest `(operator_<<        ,acc ,arg)))))))))))
    (imp args stream)))

 

このコンパイラマクロが効いている場合、先程の以下のコードがどうなるか、みてみよう。

(_<< cout showpos (setw 4) a " * "
                  (setw 4) b " = "
                  (setw 7) (* a b) endl)

 

以下のようになる。シンボルや式など、実行時点までわからないものは総称関数呼び出し、即値やマニピュレータは内部のように実装関数の直接呼び出しに展開されている。再帰呼び出しもループもなしだ。

(__os_endl
 (operator_<<
  (__ios_setw
   (__write-string
    (operator_<<
     (__ios_setw
      (__write-string
       (operator_<<
        (__ios_setw
         (__os_showpos cout)
         4)
        a)
       " * ")
      4)
     b)
    " = ")
   7)
  (* a b)))

 

今日は性能差を調べて書く元気がないのでこれで終わりにしちゃうけど、実はそれほど改善されてないんだな。もうちょっとできること、あるんだろうか。

 

コメント

このページにコメントする

 

このページのタグ

Page tag : CL-IOSTREAM

Page tag : Common Lisp

 

 


Copyright(C) 2005-2017 project-enigma.
Generated by CL-PREFAB.