2018-09-02-01-applyを追加したいのだけど
>> Site top >> weblog >> 月別アーカイブ >> 2018年09月のlog >> 2018-09-02-01-applyを追加したいのだけど
最終更新日付:2018/09/02 01: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-2021 project-enigma.
Generated by CL-PREFAB.