Ribbon: readの最適化?
とりあえず read
がやたら遅いということで、最適化を考える。
14KBくらいのソースコードを読むのに:
real 1m7.207s
user 1m9.091s
sys 0m2.342s
1分...
ちなみにGaucheで同じreaderを動かした場合は
real 0m0.409s
user 0m0.358s
sys 0m0.140s
400ms。せめて数秒とかのレベルになってくれないと厳しいもんがある。
とりあえず小手先の最適化
string-append
と substring
が一旦リストを経由していたので、VM側で直接実行するように変えた。基本的にオブジェクトに対するO(N)処理はしない方針なので。
real 0m40.066s
user 0m41.655s
sys 0m1.966s
... 予想以上に早くなったな。。ただ、これでI/Oのオーバーヘッドが無くなったと仮定してもまだGaucheに100倍負けている。
プロファイルもどきを取る
グローバル変数へのアクセスの数を数えてみる。これはアクセスをテキストファイルに書き出して sort
uniq
すれば簡単にカウントできる。
$ sort out.txt | uniq -c | sort -nr
3091422 (GET-VAR: eqv?)
1923331 (GET-VAR: rib?)
1923331 (GET-VAR: field2)
1669599 (GET-VAR: pair?)
1583213 (GET-VAR: id)
1301569 (GET-VAR: arg1)
1271658 (GET-VAR: $flonum?)
911081 (GET-VAR: require-pair)
738177 (GET-VAR: memv)
649655 (GET-VAR: null?)
636536 (GET-VAR: $fx=)
632571 (GET-VAR: =)
474495 (GET-VAR: field0)
474495 (GET-VAR: car)
436586 (GET-VAR: field1)
436586 (GET-VAR: cdr)
265296 (GET-VAR: close)
225431 (GET-VAR: vector?)
225431 (GET-VAR: require-vec)
142535 (GET-VAR: vec-ref)
125017 (GET-VAR: vector-ref)
101189 (GET-VAR: vec-set!)
100414 (GET-VAR: vector-set!)
最適化としては memv
の削減かな。。これは case
に由来するはずなので、適当なマクロを書いて or
に開いてしまえば要らなくなる。というかそもそも論として、これ何とか末尾再帰に書き直せないだろうか。。
あと $flonum?
は多分 =
の実装に由来していて、これをScheme側で実装するのはちょっと重すぎるからVM側に移しても良いかな。
=
をネイティブ実装
real 0m33.826s
user 0m34.873s
sys 0m1.982s
当初のコストより半分くらいまで来たけどまだまだ遅いな。。
$ sort out.txt | uniq -c | sort -nr
2458851 (GET-VAR: eqv?)
1923331 (GET-VAR: rib?)
1923331 (GET-VAR: field2)
1669599 (GET-VAR: pair?)
1583213 (GET-VAR: id)
1301569 (GET-VAR: arg1)
911081 (GET-VAR: require-pair)
738177 (GET-VAR: memv)
632571 (GET-VAR: =)
474495 (GET-VAR: field0)
474495 (GET-VAR: car)
436586 (GET-VAR: field1)
436586 (GET-VAR: cdr)
265296 (GET-VAR: close)
225431 (GET-VAR: vector?)
225431 (GET-VAR: require-vec)
142535 (GET-VAR: vec-ref)
125017 (GET-VAR: vector-ref)
101189 (GET-VAR: vec-set!)
100414 (GET-VAR: vector-set!)
case
を eqv?
に開く
case
構文を or
と eqv?
に開くのは syntax-rules
で簡単にできる。
(define-syntax $case/clause
(syntax-rules (else)
((_ arg (clauses ...) (else frm ...) clause ...)
($case/clause arg (clauses ... (else frm ...)) clause ...))
((_ arg (clauses ...) ((query ...) body ...) clause ...)
($case/clause arg (clauses ... ((or (eqv? 'query arg) ...) body ...)) clause ...))
((_ arg (clauses ...))
(cond clauses ...))))
(define-syntax $case
(syntax-rules ()
((_ arg clause ...)
(let ((__1 arg))
($case/clause __1 () clause ...)))))
ただこれだけやっても
real 0m15.874s
user 0m16.154s
sys 0m1.888s
こんなもんか。。せめてもう半分いきたいところだが。。
910791 (GET-VAR: eqv?)
659207 (GET-VAR: =)
463071 (GET-VAR: arg1)
422556 (GET-VAR: id)
352581 (GET-VAR: rib?)
352581 (GET-VAR: field2)
276574 (GET-VAR: close)
236325 (GET-VAR: vector?)
236325 (GET-VAR: require-vec)
219007 (GET-VAR: arg2)
149165 (GET-VAR: vec-ref)
130908 (GET-VAR: vector-ref)
106217 (GET-VAR: vec-set!)
105417 (GET-VAR: vector-set!)
とりあえずこんなもんで
real 0m13.741s
user 0m14.061s
sys 0m0.623s
まぁインライン化とか最適化を実装していけば実用的な範囲に乗るだろう。。
他のSchemeでも試す
VMはyuniで記述しているので、常識的なSchemeであれば割とどこでも動く。
Gauche
$ time cmake -P _run.cmake check.sps
real 0m41.761s
user 0m42.381s
sys 0m0.061s
↑ より遅いのはLinuxの仮想マシンで実行しているため。。だと思う。でもちょっと遅くなりすぎだなコレ。。
Chez
$ time cmake -P _run.cmake check.sps
WARNING: Using FAKE implementation of weak-box
WARNING: MEMORY LEAK will occur
real 0m1.394s
user 0m1.362s
sys 0m0.030s
流石最強Scheme。。Gaucheの10倍早い。まぁJITCだし。。あとR6RSなのでunbound variableに厳しくて修正の必要があった。
s7
TinyScheme由来だったかな。。オーディオエディタsndの内蔵Schemeでパブリックドメインかつシングルソース、高い移植性が特徴。
流石にGaucheより遅い。いわゆるAST型のインタプリタなので最適化が弱いのは仕方ないかな。
real 0m49.556s
user 0m49.469s
sys 0m0.064s
Sagittarius
s7とほぼ同じ速度。
$ time cmake -P _run.cmake check.sps
real 0m48.563s
user 0m49.444s
sys 0m0.088s
Digamma
現在のYpsilon。userがかなり長いのはコンカレントGCだから。。?
$ time cmake -P _run.cmake check.sps
real 0m39.737s
user 0m59.172s
sys 0m0.554s