型宣言とかメソッドのディスパッチとか

2週間ほど前、複数のプログラミング言語(処理系)でメソッドの起動時間を比べるという、
エントリが一部で盛り上がっていたらしいです。

既ににいろいろな言語で計測が行われているので、元のエントリとは趣向を変えて、
Common Lispのみについて、書き方による速度の変化を見てみました。
計測にはMac Book (Core 2 Duo 2GHz)で SBCL 1.0.44を使用しました。

計測結果はこちら。

元のプログラム 20.43 秒
型宣言を追加 14.897 秒
総称関数を普通の関数に変更 8.179 秒
インスタンスをクロージャに変更 3.558 秒
スペシャル変数を使用 3.805 秒

元のプログラムはこれです。

(defclass looping () ((n0 :initform 0 :accessor n0-of)))

(defmethod calc ((self looping) (n integer))
   (let ((n1 (+ (n0-of self) (- 1 (* 2 (mod n 2))))))
      (setf (n0-of self) n)
      n1))

(let ((l (make-instance 'looping)) (n 1) (t1 (get-internal-real-time)))
   (dotimes (c 268435455)
      (setq n (calc l n)))
   (print (float (/ (- (get-internal-real-time) t1) internal-time-units-per-second))))

手始めに型宣言を付けました。

(defclass looping () ((n0 :initform 0 :accessor n0-of :type fixnum)))

(defmethod calc ((self looping) (n integer))
  (declare (optimize (safety 0) (speed 3)))
  (declare (fixnum n))
  (let ((n1 (the fixnum (+ (the fixnum (n0-of self)) (- 1 (* 2 (mod n 2)))))))
    (declare (fixnum n1))
    (setf (n0-of self) n)
    n1))

型をfixnumに固定するだけで結構速くなります。

次に、defmethodをdefunに変更。

(defun calc (self n)
  (declare (optimize (safety 0) (speed 3)))
  (declare (fixnum n))
  (declare (looping self))
  (let ((n1 (the fixnum (+ (the fixnum (n0-of self)) (- 1 (* 2 (mod n 2)))))))
    (declare (fixnum n1))
    (setf (n0-of self) n)
    n1))

元のエントリの趣旨から完全に外れてしまった感じですが、
ディスパッチが不要になるとかなり速くなります。

本来の趣旨からどんどん離れてクロージャとか使っちゃいます。

(let ((self 0))
  (declare (fixnum self))
  (defun calc (n)
    (declare (optimize (safety 0) (speed 3)))
    (declare (fixnum n))
    (let ((n1 (the fixnum (+ (the fixnum self) (- 1 (* 2 (mod n 2)))))))
      (declare (fixnum n1))
      (setf self n)
      n1)))

(time (let ((n 1) (t1 (get-internal-real-time)))
        (dotimes (c 268435455)
          (setq n (calc n)))
        (print (float (/ (- (get-internal-real-time) t1) internal-time-units-per-second)))))

めちゃくちゃ速くなりました。

最後に、クロージャすら使わずにスペシャル変数を使いました。

(defvar *self* 0)
(declaim (fixnum *self*))
(defun calc (n)
  (declare (optimize (safety 0) (speed 3)))
  (declare (fixnum n))
  (let ((n1 (the fixnum (+ (the fixnum *self*) (- 1 (* 2 (mod n 2)))))))
    (declare (fixnum n1))
    (setf *self* n)
    n1))

意外なことに、クロージャ版より遅くなってしまいました。
ダイナミックスコープに備え、なにか面倒なことをしてるんでしょうか。

もっと速くする方法を知ってる方がいましたら、ぜひとも教えてください。

Leave a Reply