2018-09-02-01-applyを追加したいのだけど - project-enigma

2018-09-02-01-applyを追加したいのだけど

>> Site top >> weblog >> 月別アーカイブ >> 2018年09月のlog >> 2018-09-02-01-applyを追加したいのだけど

最終更新日付:2018/09/02 22:00:00


applyを追加したいのだけど

2018 年 09 月 02 日

C++17 では、std::apply が追加された。std::tuple の内容をパラメータとして、関数を呼び出せるものらしい。これを CL-STL に追加したい、という話。しかし、内部表現の変換コストが気になってしまう、という話。

CL-STL の stl:tuple は(stl:pair もだけど)、内部的には cl:simple-vector で実装されている。要するに配列だな。std::apply を愚直に実装しようとすると、cl:apply に転送するイメージになり、結果として cl:list への coerce が必要になる。このコストが気になるのだ。

まずはこの「愚直な実装」から。ひとまずは apply-test-1 という関数名にすると、以下のようになる。

(defun apply-test-1 (fnc tpl)
  (cl:apply fnc (coerce (stl::__inner-array tpl) 'cl:list)))

 

とりあえず、動かしてみよう。

(defun test-func (a b c)
  (+ a b c))
 
(let ((tpl (stl:make_tuple 111 222 333)))
  (apply-test-1 #'test-func tpl))
 
=> 666

 

上記の実行によって、3要素の配列から cl:list への変換が内部で発生している。静的に型付けされた C++ とは違って、stl:tuple の要素数は実行時にしかわからないから、これは仕方がないように見える。

しかし、ある程度の範囲で良く使用する要素数についてはベタな funcall に展開することで cl:list への変換を避けることはできるだろう。ひとまず2〜4についてやってみると、以下のようになる。

(defun apply-test-2 (fnc tpl)
  (let ((arr (stl::__inner-array tpl)))
    (let ((cnt (length arr)))
      (case cnt
        (2 (cl:funcall fnc (svref arr 0) (svref arr 1)))
        (3 (cl:funcall fnc (svref arr 0)
                           (svref arr 1) (svref arr 2)))
        (4 (cl:funcall fnc (svref arr 0) (svref arr 1)
                           (svref arr 2) (svref arr 3)))
        (t (cl:apply   fnc (coerce arr 'cl:list)))))))

 

宣言を追加して optimize した版も書いてみた。ここらへん良くわかってないので、もっとやるべきことがあるかもしれない。

(locally (declare (optimize speed))
  (defun apply-test-3 (fnc tpl)
    (let ((arr (stl::__inner-array tpl)))
      (declare (type simple-vector arr))
      (let ((cnt (length arr)))
        (declare (type fixnum cnt))
        (case cnt
          (2 (cl:funcall fnc (svref arr 0) (svref arr 1)))
          (3 (cl:funcall fnc (svref arr 0)
                             (svref arr 1) (svref arr 2)))
          (4 (cl:funcall fnc (svref arr 0) (svref arr 1)
                             (svref arr 2) (svref arr 3)))
          (t (cl:apply fnc (coerce arr 'cl:list))))))))

 

で、簡単だけどこれらを比較してみる。

(locally (declare (optimize speed))
  (defun test-func (a b c)
    (declare (type fixnum a b c))
    (the fixnum (+ a (the fixnum (+ b c))))))
 
(let ((tpl (stl:make_tuple 111 222 333)))
  (dolist (apply-fnc (list #'apply-test-1
                           #'apply-test-2
                           #'apply-test-3))
    (time (dotimes (i 1000000)
            (funcall apply-fnc #'test-func tpl)))))

 

結果は以下の通り。上から順番に愚直な coerce、要素数に応じた funcall展開、そしてその宣言付き optimize 版。ひとまず効果は出ているようだ。

=> Evaluation took:
     0.045 seconds of real time
     0.045926 seconds of total run time (0.045926 user, 0.000000 system)
     [ Run times consist of 0.005 seconds GC time, and 0.041 seconds non-GC time. ]
     102.22% CPU
     118,766,619 processor cycles
     48,005,120 bytes consed
     
   Evaluation took:
     0.020 seconds of real time
     0.020468 seconds of total run time (0.020468 user, 0.000000 system)
     100.00% CPU
     52,618,399 processor cycles
     0 bytes consed
     
   Evaluation took:
     0.014 seconds of real time
     0.013545 seconds of total run time (0.013545 user, 0.000000 system)
     100.00% CPU
     35,179,934 processor cycles
     0 bytes consed

 

自分としてはどれくらい使うかわからないのだけれど、ひとまずこれで行ってみようかな‥‥‥と思っている。

 

コメント

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

 

このページのタグ

Page tag : STLとその移植

Page tag : Common Lisp

 

 


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