梅雨の季節と思いきや、清々しい天候の続くこの頃、皆様いかがお過ごしでしょうか? Tech開発部アーキテクチャグループで色々やっております塚本と申します。普段はスクラムマスターやったりしています。
もうひと月以上経ってますが、先日参加してきたRubyKaigiのセッションの内容を掘り下げて記事にしてみます。 Kaigiの概要は村上さんの記事をご参照ください。
tl;dr;
- Rubyはよい
- 個人的にはコミッターがよい(失礼)
- 発表もよい
- 記事にしたいセッションが絞りきれず執筆が大変なことに
- Rubyコアの速度改善の話に絞りました
- 他はまた別途
Rubyに対して
私のRuby歴は全部足して3年くらいで、関わってきたコミュニティの皆さんほど言語に対する思い入れもなく、Railsなんかツラい思い出が9割くらいなんですが、Rubyはいい言語だと思います。 端的にいうとMatzが「プログラミングを楽しむこと」を重視していて、それをRubyコミュニティの皆さんも体現している。 そこが素晴らしいと思います。 ところが私の知る限り、いつの世もプログラミング言語の宗教戦争は絶えず、Rubyもその舌渦の只中におります。これまでPythonやGolangを書いていたときのほうがツライ思い出は少ないせいで、「Rubyじゃなくてもいいんじゃないか?」と思うことも私自身よくありますが、それでもRubyはよい言語です。これだけは言っておきたいのですが、「よい言語かどうかを決めるのに他言語と比較する必要はない」ということです。
Rubyの速さ
特に以前、Railsアプリで大量のバッチ処理を扱っていたときに並列処理できず単体プロセスの処理速度にも問題を抱えていたこともあり、Rubyの速さに関しては気になっていました。 当然コミッタの方々も長年その問題に取り組んできていて、マルチコアへの対応などありますが、今回はJIT周りにフォーカス。
JITコンパイラによるRuby高速化の話
Performance Improvement of Ruby 2.7 JIT in Real World
Ruby2.6からマージされている JITですが、「RailsではメソッドがJITコンパイルされるほど遅くなる」というIssueが昨年から提起されています bugs.ruby-lang.org
今、Ruby 2.7に向けてそこの改善に取り組んでいる戦いの様子を@k0kubunさんが発表
ということで、問題切り分けのためコンパイルが全て終わった状態でRailsをベンチマークすると、それでもまだ微妙に遅くなっている
* このあとの、Noahgibbsさんのスピーチでも出てきますが、DiscourseというRailsアプリで計測するのがお作法っぽい
コンパイルに死ぬほど時間かかる問題は置いておいても、なぜJITによって速くならんのか? そこを分析していくお話(本題やっとここから)
- discourseはプロファイルするために安定状態にもっていくスタンバイに時間が食うため効率が悪い
- そこで別のリポジトリ(Railsbench)を用意
- Railsbenchで計測し直してみるとやっぱり遅い
- 2.7になるとマシなのでJITの方向性は間違ってなさそう
- よっしゃがんばっていろいろ試していこう
その取り組みが以下5本
- Profile-guided Optimization
- Optimization Prediction
- Deoptimized Recompilation
- Frame-omitted method Inlining
- Stack-based Object Allocation
それぞれ丁寧に説明してくれてましたが、ところどころ難解な箇所あったので、初心者でもわかるレベルに落とし込んでます。
1. Profile-guided Optimization
問題としては、JITedメソッドをたくさん呼ぶほど遅いこと
そこで Profile-guided Optimization
平たく言うと、GCCコンパイル時にプロファイル生成を挟むといい感じに最適化されるだろうという方法。
がしかし、Railsが遅い問題の解決にはならない(という気づきを得た)。
よって今回はこの方法は見送り。
2. Optimization Prediction
JITコンパイルしても効率が悪いようなメソッドを予測して、それらをコンパイルしないように除いたりできるだろう がしかし、これも効果としてはうまくいかない。。
3. Deoptimized Recompilation
- JITキャンセルという挙動*1が起こるほどにオーバーヘッドになる
- 全体の1/6はキャンセルされている(Railsbench計測)
- 特にライブラリの中によくあるブラケットメソッド*2の実装がキャンセルが起きやすい
- このブラケットメソッドを多重継承するとInvalidatedされる
- Railsではこのような多重継承するコードが大量になるため多くのキャンセルが起きる
という問題でした。
この問題は元々ある、キャンセルの最適化を無効化する機能を少しシンプルにすることで解消できる。 そしてこれは効果があった!
4. Frame-omitted method Inlining
Rubyはシンプルにメソッドコールが遅い(Rubyでは有名な話)*3
これはシンプルにメソッドのインライン化で解決できる
- JITが生成したコードの関数ポインタを呼び出す代わりに、まったく同様のCの関数定義を書いてインライン関数として呼び出す(めんどくさくない?)
- メソッド呼び出しでcallスタックに積まれていくやつを積まなくする(ことによって)メソッドがなかったかのような振る舞いをする
2番目のほうがJITとメソッドインライン化による正当な恩恵ですね
ただ、メソッドのインライン化をするためには、対象のメソッド呼び出しが副作用を持たないことを判定しないといけない。 副作用を持たないのであれば、コアのクラスのメソッドをRubyで書き直したり、JIT使うことにより効果がある
5. Stacked-based Object Allocation
- オブジェクトを作れば作るほど遅くなる
- Railsアプリはオブジェクトでいっぱい
JITはオブジェクトをStackに積むことができる
なので、ローカル変数で渡すのでなく、オブジェクトがループ内で何度も作られていたようなコードに効くようになる
ただしこれもメソッドの外側に参照が飛ばないようになっている(escapeしない)条件がある ↑ ここさらっと書いてますが、まったくもって大変なことを解析している
結論
- 愚直に改善を試行しつつ確実に前進しているのすごい(凄さを伝えられない)
- Rubyプログラマに
.freeze
のようなマイクロ最適化を毎回させたくない(by @k0kubun氏)
こういう猛者がユーザのために日々がんばっているRubyは、いちユーザから見たプロダクトとしてもよい!
Railsの速度改善との6年間の格闘の歴史
Six Years of Ruby Performance: A History
ベンチツールを開発してきているNoahGibbsさんの発表
6年間のグラフ
ここ数年はRails Ruby Benchというベンチマークツール*4(以下RRB)を使用してきた。
バージョンを重ねるごとに着実に速くなっている
ところがRRBでは、複雑に構成されたアプリケーションのどこが一体遅いのか、はっきりとわからない。
- RRBはそれ自体が大きすぎるという問題があり
- どこに問題があるのかspecificにするならば小さいベンチマークツールも必要ではないか
そこでより小さく問題を検出するためにRSB
RSBで計測していくと見えてくること
- スタティック文字列を返すだけのシンプルなRailsアプリでも実はそんなに早くなってない
- 早いのはPumaだった
こういうわけでRailsにはAPIモードがある。 通常装備のRailsにはオーバーヘッドが多すぎる
まずはフレームワークのオーバーヘッドというのが1つ目
次にConcurrencyについてみていくと
スレッドはよくないのか?
ただし、プロセスをどんどん増やすと逆転し、マルチスレッドのほうが速くなる。 これは、ベンチマークツールのワークロードに依存するのが原因とのこと。
結論として、Rubyは並行処理をプロセスもスレッドも同じ方法でハンドリングしている。 このポイントに関しては、Guilds(仮)に期待していきましょう!
以上、Cocurrencyについての計測が2つ目
今回はこの二点に関するマイクロ計測の発表だけでしたが、
- MJIT
- GC profiling / Memory Usage
なども今後結果をお知らせしてくれるそうです。
まとめ
Rubyはこれまで地道に改良を続けてきていますが、さらにその裏で正しく言語処理速度を計測する仕組みをチューニングし続けるNoah Gibbs氏のような人もいて、Rubyを支える人たちの深みや厚みを感じる発表でした。
笹田さんのRubyのインタープリタをRubyで書く話
https://rubykaigi.org/2019/presentations/ko1.html#apr18
超面白かったんですが、ボリュームやばそうなので、今回はここまで。
@_ko1 さんのRubyでインタプリター書く!
— Hisahiro Tsukamoto (@hihats) April 18, 2019
と @joker1007 さんのモナディックプログラミングのどちらかしか聴けないという酷な選択を強いられてる #rubykaigi
このペースだとあと5記事くらい必要そうで全部終わる頃には来年のRubyKaigiになっていそうです。
余談
私自身、福岡には縁があって、1年ほど住んでたこともあるし訪れたことも10回以上ありますが、何度来てもここは最高です。
こちらからは以上です。