2018/07/13 の ruby r63959 現在、MJIT からローカルスタックがなくなっているので、これを復活させたかったのだけどうまく行かなかった、という作業の記録です。 今回は珍しく作業は終えていてあとは文章にまとめるだけになっています。
書き始め当初は、時系列に作業記録を書こうと思っていたのですが、作業当時にメモをつけておらず、作業を再現するだけならできるだろうと思っていたのですが全然できなかったので、「作業記録」というより「作業内容」=「やったこと一覧」になっています。すみませんすみません。
やっぱり作業時にはリアルタイムにメモをとらんといかんと思います。
なんでこれでうまく行かないのかよくわからないので識者にツッコミが頂ければありがたいなあという浅ましい考えのもとこの文章を書いています。
この文章中で特に断りなく MJIT と書いた場合、ruby の trunk にマージされた YARV-MJIT のこと、またこのマージによって trunk に追加された JIT 機能のこと、を指すことにさせてください。 本来であれば vlad さんの実装の元祖 MJIT を指すべきなのかもしれませんがタイプ量を減らしたいので。
2018/06/27、r63763 というコミットが MJIT に衝撃を与えました。r62051 / r62087 などのコミットをリバートするというものです。 いやすみません「衝撃」は大げさな表現かもしれません。
とにかくこれ以降しばらくの間、JITed なコードが正しく期待通り動かない状態になっていました。 r63863 / r63874 / r63875 という一連のコミットで再び MJIT は正常に動き始めましたが、この r63863 というコミットは r62655 を諦めてリバートするコミットでした。
これによって、MJIT はローカルスタック(と私が勝手に読んでいる最適化)を一時的にではありますが失ってしまいました。
それぞれのコミットの内容を簡単にまとめます。
- r62051: VM が命令列を解釈するときの、PC(プログラムカウンタ)の移動のさせ方を変えています。
- 大体の命令で、今まで PC を移動させてから命令を解釈していたのを、命令を解釈してから PC を移動させるようにしています。
- r62087: VM が命令列を解釈するときの、SP(スタックポインタ)の移動のさせ方を変えています。
- 大体の命令で、命令解釈中に必要に応じて随時 SP を移動させていたのを、命令解釈前に最終的な位置に一気に移動させるようにしています。
- r62189: MJIT のマージ。
- つまり MJIT はマージ当初から、r62051 や r62087 を前提にした作りになっていたのでした。
- r62655: MJIT にローカルスタック最適化が導入されました。
- これは、MJIT の JITed なコード中で今までは VM のスタックポインタを使っていたところを、ローカル変数で
VALUE stack[xxx];のような形で作った配列をスタックとして利用するようにする最適化です。 - なんでこれで速くなるのかはあんまりよくわかっていないんですが、SP の加減算を減らせるとか、C コンパイラ側でうまく扱ってくれるとか、malloc() で用意されたヒープ領域よりマシンスタック領域のほうがアクセス速度が速いとか、そういうことな気がしています。
- これは、MJIT の JITed なコード中で今までは VM のスタックポインタを使っていたところを、ローカル変数で
- r63763: r62051 や r62087 のリバート。
- バックトレース等々でうまく行かないところがあって r62051 のリバートにいたり、それを前提にしていた r62087 も道連れにリバート、ということのように読めます。やむなし。
- ということで、PC / SP の移動のさせ方が変わってしまったので MJIT もそれに追随しなければいけなくなったのでした。
- r63863: r62655 のリバート。
- SP の移動の仕方が変わったのでローカルスタックもそれに追随しないといけないのですが、なかなか困難だったのだろうと推測されます。
- というか自分もやってみてわかったのですがとても困難です。やれないことはないかもしれないですがとても面倒です。
- MJIT のメンテナの k0kubun さんも何度か「一時的に」リバートすると言及されていた気がするので、そのうち再適用されると思います。
- SP の移動の仕方が変わったのでローカルスタックもそれに追随しないといけないのですが、なかなか困難だったのだろうと推測されます。
このような時系列だったと認識しております。 で、待っていればそのうちにまたローカルスタックは復活するのでしょうけれども、もし早めに戻せたらよくないか?ということで作業をはじめたのがことの始まりです。
前置きが長くなりました。ここからやったこと一覧です。
https://github.com/wanabe/ruby/commit/3523886a03e378fb6d6a4663545af863e98e1ebf こちらです。 テストはそのまま残す、r63874 / r63875 と衝突するところはうまくやり過ごす、などしました。
https://github.com/wanabe/ruby/commit/8e98801c5fd611de97fe6e827cfea04dde8626a2 こちらです。 マクロをローカルスタック仕様に書き換える部分を差し戻しました。
https://github.com/wanabe/ruby/commit/0c70cc64acd85d32cf7e3f0bdc1c2c1c23fadfcb こちらです。
cfp->sp を移動させている部分は insns.def に入れるようにして vm_expandarray() 内では sp を移動させないようにすることで、cfp をあw足す必要をなくしました。
https://github.com/wanabe/ruby/commit/2699f827662cee2c3b09ba3f05ea6c155c500697 こちらです。 VM で命令が実行されるときにはスタックから命令の引数の分だけ pop するようになっており、MJIT でも同じ扱いにしないとずれてしまうので、その分だけ減算するようにしました。
できあがったものが https://github.com/wanabe/ruby/tree/local-stack こちらで、make exam は通るようにはなりました。
なんですが、全く速度改善がされませんでした。むしろ遅くなっています。
$ bundle exec benchmark-driver benchmark.yml --rbenv 'local-stack --jit;trunk --jit' --repeat-count 10 --verbose |tee result.log
local-stack --jit: ruby 2.6.0dev (2018-07-09 local-stack 63907) +JIT [x86_64-linux]
last_commit=Set MJIT stack_size that pos is before insn execution
trunk --jit: ruby 2.6.0dev (2018-07-09 trunk 63907) +JIT [x86_64-linux]
Calculating -------------------------------------
local-stack --jit trunk --jit
optcarrot 54.305 54.807 fps
Comparison:
optcarrot
trunk --jit: 54.8 fps
local-stack --jit: 54.3 fps - 1.01x slower
r62655 のコミットログを見るとローカルスタック導入前後で 18% くらいの改善があるということなのですが、自分の作業では全く速度改善しませんでした。 これはつまり自分の書いた部分でなにかおかしい部分があるに違いないとは思うのですが、しかし全く見当がつきませんでした。
という、うまくいかなかった話です。結局どのへんに問題があるのかなあ。