mruby v1.3
mruby v1.3 がリリースされましたね。
趣味mrubyウォッチャーとしてv1.2からv1.3で何が変わったのかを、個人的にまとめてみたいと思います。
注目すべきは、やはりmatzのcommit数。
もちろんmerge commitも含みますが、約半数のcommitがmatzのcommitになっています。
なぜmatzがここまでmrubyに力を入れるのか聞いてみたいところですね。
それではmruby v1.2 からv1.3への変更で何が変わったのか、ザックリ見ていこうと思います。
リリースノート
http://mruby.org/releases/2017/07/04/mruby-1.3.0-released.html
1年以上あった割には、表向きにはそこまで変化はない感じ? わりと最新のCRubyの文法やメソッドも入っていたりしますね。
Contributions
https://github.com/mruby/mruby/graphs/contributors?from=2015-11-17&to=2017-07-04
v1.2が出た日からv1.3が出た日にしているので若干正確ではないのですが、やはりmatzのcommit数はダントツですね。
Many bug fixes
やはり注目すべきはShopifyからの大量のbug報告と、これら全てを丁寧に対応されたmatzの修正の応酬でしょう。
https://github.com/mruby/mruby/issues?q=is%3Aissue+author%3Aclayton-shopify
SEGVの報告。free後のメモリや、確保した領域外のメモリなど、触れてはいけないメモリを触れてしまっている部分の修正。GCのバグ修正。意図しない無限ループの修正。諸々含めて約200のissueが上げられ、閉じられています。
shopifyとmrubyの関係はbovi氏のblogが詳しい。
The 500.000$ release - mruby.sh
こちらからの目線での印象としては、正直「そんな重箱の隅まで……。」というケースもあったのですが、 ユーザーにコードを書いてもらって実行するようなシステムだと、「ユーザーにシステムを止められるようなコード書かれると困る」というのは分かる話。
おかげでmruby本体が原因でSEGVが起きるようなケースは、重箱の隅を含めてもそう滅多なことでは起こらないようになったんじゃないでしょうか。
mrb_yieldでbreakできない問題
折角なのでどういうbug fixがあったのかひとつ抜き出してみましょう。
この問題が修正されたのは1.3が出る直前のことでした。
v1.2では、実は以下のようなコードはうまく動きません。 breakしているのに、ループが終わらないのです。っていうかSEGVします。
$ mruby-1.2.0 "aaa\nbbb\nccc\n".lines do |line| p line break end #=> "aaa\n" #=> "bbb\n" #=> "ccc\n" [2] 72458 segmentation fault mruby-1.2.0
これは、Cで実装されたメソッドでmrb_yield
系が使われていた場合にbreak
を利用すると発生していました。
break
をreturn
とほぼ同じ扱いにしているので、rubyで書かれたブロックならbreak
で以降のコードは飛ばせるのですが、
C側のコードは引き続き実行してしまうのでループを止められないという問題があったようです。
Rubyコード上はbreak
しているのに、C側のコードは止められないので不整合が起こり、SEGVしているものと思われます。
この問題はbreak
を例外扱いすることでlongjumpし、Cのレベルでもメソッドのコードを飛ばせるように修正されています。
Allow `break` from a block called by `mrb_yield`; close #3359 · mruby/mruby@d4d99dd · GitHub
パフォーマンス
「パフォーマンスは早くなったの?」
ということで、mrubyに添付されているbenchmarkスクリプトで計測してみました。
buildの設定はfull-coreにしただけでその他は初期値です。(OSX 10.12.5 clang v8.1.0)
$ time mruby-1.2.0 benchmark/bm_ao_render.rb > /dev/null mruby-1.2.0 benchmark/bm_ao_render.rb > /dev/null 20.22s user 1.44s system 98% cpu 21.942 total $ time mruby-1.3.0 benchmark/bm_ao_render.rb > /dev/null mruby-1.3.0 benchmark/bm_ao_render.rb > /dev/null 19.74s user 0.11s system 98% cpu 20.134 total $ time mruby-1.2.0 benchmark/bm_fib.rb > /dev/null mruby-1.2.0 benchmark/bm_fib.rb > /dev/null 12.32s user 0.03s system 99% cpu 12.387 total $ time mruby-1.3.0 benchmark/bm_fib.rb > /dev/null mruby-1.3.0 benchmark/bm_fib.rb > /dev/null 13.33s user 0.05s system 98% cpu 13.529 total $ time mruby-1.2.0 benchmark/bm_app_lc_fizzbuzz.rb > /dev/null mruby-1.2.0 benchmark/bm_app_lc_fizzbuzz.rb > /dev/null 37.46s user 0.45s system 98% cpu 38.559 total $ time mruby-1.3.0 benchmark/bm_app_lc_fizzbuzz.rb > /dev/null mruby-1.3.0 benchmark/bm_app_lc_fizzbuzz.rb > /dev/null 41.14s user 0.37s system 99% cpu 41.850 total $ time mruby-1.2.0 benchmark/bm_so_lists.rb > /dev/null mruby-1.2.0 benchmark/bm_so_lists.rb > /dev/null 5.90s user 0.17s system 97% cpu 6.197 total $ time mruby-1.3.0 benchmark/bm_so_lists.rb > /dev/null mruby-1.3.0 benchmark/bm_so_lists.rb > /dev/null 48.57s user 0.53s system 99% cpu 49.492 total
うーん、そこまで劇的な変化はなさそうですね。
やはり、mruby v1.3の目玉は大量のbug fixということでしょうか。
bm_so_lists.rbはCRubyだと1sとかなのでこれは……見つけちゃったかもしれないですね……。
一応報告しておきましょう。
Performance regression for benchmark/bm_so_lists.rb · Issue #3737 · mruby/mruby · GitHub
[追記]なおしました。
Should only check frozen fix #3737 by ksss · Pull Request #3739 · mruby/mruby · GitHub
v1.4……?
keyword arguments
https://github.com/mruby/mruby/pull/3629
実はkeyword argumentsに対応するPRは既に来ています。
が、v1.3には取り込まれませんでした。
リリースノートにもRemaining Bugsとしてこっそり含まれているので、ゆくゆく対応されるのではないでしょうか。
※ここは個人のブログです
個人のブログなので、ここからは自分がやったことをまとめます。
Kernel#caller
書いたのオレオレ
Cレベルではほぼ同じ機能があったので、これをRubyの世界でも使えるようにしただけ。
引数の扱いが意外とめんどくさかったりしました。
これでデバッグが楽になる場面が増えたはず……!
Proc#initialize
消したのオレオレ
これまでは、Proc#initialize
でProcオブジェクトの初期化を行っていたのですが、Proc.remove_method(:initialize)
と凶悪なことをすると、Procオブジェクトに必要な値が準備されず、いろいろな部分でSEGVが発生するようになります。
これではCレベルのメソッドでProcオブジェクトを扱う際に、常にProc.remove_method(:initialize)
されたことも考慮しないといけなくなってしまいます。
ところでCRubyを見てみると、なんとProc#initialize
は定義されていないではありませんか。
つまりこういう問題は、CRubyではProc.remove_method(:initialize)
にはProc#initialize
を定義しないことによって対策していると判断。
mrubyでもProc#initialize
を削除して、Proc.new
メソッドを独自に定義するようにしました。*1
Proc.new
で初期化するようにすれば、たとえProc.new
が消されたとしてもProcオブジェクトが作れないのだけなので問題ありません。
Contributionsの二番目にいるのオレオレ
https://github.com/mruby/mruby/graphs/contributors?from=2015-11-17&to=2017-07-04
mruby-specを使って、mrubyとCRubyの挙動の不整合をひたすら直すということをしてました。 またその際にみつかったバグなども直したり、難しすぎて直せないものはissueで報告などの活動をしていました。
序盤の活動はブログに書いていましたね。
- mrubyでruby/specを走らせることに成功した - スペクトラム
- mruby-specの成果報告 - スペクトラム
- mruby-spec進捗日記〜第二話〜 - スペクトラム
- 第三回mruby-spec進捗確認 - スペクトラム
結構Rubyの細かい挙動を知れたりして勉強にもなりました。
shopifyのやつもちょこっとだけ手伝った
Make string embad from shared by ksss · Pull Request #3657 · mruby/mruby · GitHub
例えばこちらのエントリーでは、shopify issueの https://github.com/mruby/mruby/issues/3651 を解決しています。
前知識としてStringオブジェクトは、shared, no-free, embed, normalなどの状態が存在し、今どの状態かによって適切に処理を分岐しないと、最悪SEGVするケースがあります(大抵は破壊的な操作)。
sharedなStringを変更してsharedの関係をやめるときに、これまではnormalなStringとしていたのですが、 issueにあるような一部のコードでは文字列の長さでembedかどうか判断していました。
じゃあこれを適切に場合分けして……とも思ったのですが、normalなのに長さはembed可能な文字列が生成できてしまうせいで、問題が起こっていると判断。sharedなStringを変更するときに、長さが短い場合はembedなStringにしてしまえば、問題も治るしメモリ消費を若干抑えられるしで一石二鳥です。
bugを一つ埋め込んでしまった
で報告されているとおり、mruby-string-ext
gemをMRB_DISABLE_STDIO
でbuildできなくしてしまいました。。。
MRB_DISABLE_STDIO
はその名の通り標準入出力を行う関数を一切禁止するだいぶリソースが厳しい環境向けのオプション。
そんな厳しい環境では標準添付ライブラリなんて使わないよね……。う……これで困る人、許してくれ。。。
mruby v1.3
みんな使ってね!
*1:ちなみに実装は、渡されたブロックのコピーを作っているだけ。