sbclでのmkstr実装(or 文字列出力)注意点

『On Lisp』のmkstrの話。

オリジナルの実装はこれ。

;; 引用: http://www.komaba.utmc.or.jp/~flatline/onlispjhtml/utilityFunctions.html
(defun mkstr (&rest args)
  (with-output-to-string (s)
    (dolist (a args) (princ a s))))

この実装は、sbcl(1.0.34)の場合効率的ではない。
原因は、string型(及びcharacter型)に対するprinc関数の処理速度が遅いこと(特に出力先がstring-output-stream型の場合に顕著)

;; princ関数を使った場合
> (time
   (progn
     (with-output-to-string (out)
       (dotimes (i 2000)
         (princ "abcdefghijklmnopqrstuvwxyz" out)
         (princ "あいうえおかきくけこさしすせそ" out)))
     'done))
Evaluation took:
  4.177 seconds of real time   ; 約4秒
  4.176262 seconds of total run time (4.172261 user, 0.004001 system)
  [ Run times consist of 0.016 seconds GC time, and 4.161 seconds non-GC time. ]
  99.98% CPU
  11,665,781,532 processor cycles
  7,575,720 bytes consed
--> DONE

;; write-string関数を使った場合
> (time
   (progn
     (with-output-to-string (out)
       (dotimes (i 2000)
         (write-string "abcdefghijklmnopqrstuvwxyz" out)
         (write-string "あいうえおかきくけこさしすせそ" out)))
     'done))
Evaluation took:
  0.001 seconds of real time ; 0.001秒
  0.000000 seconds of total run time (0.000000 user, 0.000000 system)
  0.00% CPU
  3,220,524 processor cycles
  654,400 bytes consed
--> DONE

これを反映したmkstr定義。

(defun mkstr (&rest args)
  (with-output-to-string (s)
    (dolist (a args)
      (typecase a
        (string    (write-string a s))
        (character (write-char a s))
        (otherwise (princ a s))))))

mkstr関数をそれほど大量の文字列に対して適用することはあまりないかもしれないが、それ以外の場合でも速度が要求される場面で大量の文字列の出力を行う場合は、write-string関数を使うよう注意が必要。