こんにちは。
メディアサービス開発部、Webアプリケーション開発課のフサギコ(髙﨑)です。
Ruby on Railsによるバックエンドの実装運用と、AWSによるサービスインフラの設計構築を中心とした、いわゆるテックリードのような立ち位置で働いています。
本記事では、2022年の9月8日から9月10日にかけて三重県津市で開催された、RubyKaigi 2022に現地参加したことについてお話しします1。
- RubyKaigiとは
- 実に3年ぶりの物理開催
- 見に行った講演の感想など
- Ruby meets WebAssembly
- Making *MaNy* threads on Ruby
- Building a Lightweight IR and Backend for YJIT
- Towards Ruby 4 JIT
- Ruby debugger - The best investment for your productivity
- Make RuboCop super fast
- Method-based JIT compilation by transpiling to Julia
- How fast really is Ruby 3.x?
- Caching With MessagePack
- error_highlight: user-friendly error diagnostics
- Syntax Tree
- The Better RuboCop World to enjoy Ruby
- String Meets Encoding
- History of Japanese Ruby reference manual, and future
- Matz Keynote コミュニティの力
- まとめ
RubyKaigiとは
RubyKaigiは、プログラミング言語Rubyにまつわるあらゆるトピックを扱う国際カンファレンスです。 2会場3日間にわたって、Rubyに関する様々な研究開発の成果や、事例発表などが行われます。
Rubyが日本発の言語ということもあり、世界の様々な地域で開催されているRubyについてのカンファレンスの中でもトップクラスに大規模で、日本国外からの参加者も(僕個人の体感ですが)2~3割ほどいます。
2016年以降は首都圏ではなく日本各地で開催されている点も特徴的ではないでしょうか。
実に3年ぶりの物理開催
僕個人としては、前職のドワンゴ2在籍時代に参加した2018年の仙台、2019年の福岡に続いて3回目の現地参加でした。
当時は当然に開催されるだろうと信じていた2020松本が新型コロナウィルスの影響で中止され、その代替としての2020 Takeoutの開催、翌年2021も現地開催は予定されずTakeoutでの開催。 どうにかRubyKaigiを続けようと努力されたオーガナイザーの皆さんには敬意を表します。
しかしその一方で、最後のクロージングで松田さんもお話されていたように、地域Ruby勉強会も、RubyKaigiもオンラインになって、 既に面識がある人たちと物理的に会えない、面識がなかった人たちとRubyの話を通じて仲良くなれない、 という点からRubyコミュニティからの隔絶、孤独感を少なからず感じた期間だったとも思います。
それが今回、実に3年ぶりに物理で、オフラインで開催されたのです。 これは僕にとっても大きなことでした。
見に行った講演の感想など
今回見に行ったのは下記のような感じでした。
Ruby meets WebAssembly
今年の4月ごろに突然流星のように訪れたRubyのwasm実装とマージ。その実装者であるkateinoigakuさんによるキーノート。
前半はブラウザ上でscript要素内に直接rubyを書くだけでrubyが動く、ブラウザ上でirbが実行できSVGによる図形描画ができる、というデモ。
中盤でRubyをWasmで動作させるにあたってWasmの実行モデルから来る制約による難しさ、その回避策としてのAsyncifyなるテクニックの話。
セキュリティの関係で読めないようにされているというwasmのスタック領域がAsyncifyなるテクニックを使うとどうして間接的に観測できるようになるのか、まではその場ではちょっと理解しきれませんでした。
最後にWasmはNon Browser領域にも採用されつつあるという話、そのための標準インターフェイスとしてWASIが策定されている話、クラウドフレアやFastlyでエッジコンピューティングとしてサポートされている話、そのデモ。
ともあれ、これまでほぼ完全にサーバサイドのみのものであったRubyがブラウザ上でも、エッジコンピューティングでも動作させられるようになったというのは、その実用例としてCookpadさんのコードパズルが公開されたりしたことも含めてRubyの可能性を大きく広げる話だな、と思いました。
#rubykaigi opalみたいにRubyスクリプトをjsにコンパイルとかじゃなくてscript要素にtype="text/ruby"と指定したらそのまま書けるんだなぁ pic.twitter.com/0bH2iJDUpU
— フサギコ (@fusagiko) 2022年9月8日
kateinoigakukun.hatenablog.com
Making *MaNy* threads on Ruby
スライドはこちら。
Rubyでより大量にスレッドを作れるようにするための実行モデルの変更の検討に関する話。
RubyもM:Nスレッドモデルに行こうとしているのはまあ、ある種の必然というか納得でした。 M:Nスレッドモデルのうち、基本的にはRactorがMを担当し、各Ractor内では1:Nを保つことで後方互換性を維持する、という理解をしました。
これによって後方互換は問題なく保たれることが期待できる一方で、Rubyの実行コンテクストとしての"Thread"と、OS機能としてのThreadが名称として被っていてどちらを指すのか都度確認しなければならなくなるのは注意が必要になるだろうとも思いました。
いち開発者としては、PumaでRackアプリケーションを並行並列処理させる際にプロセス数とスレッド数をそれぞれ指定・調整するのがなかなかに面倒だと感じていて、そのあたりがRactorとM:N実行モデルによって改善されるといいなぁ、と期待しています。 まあ、Ractorの共有可能オブジェクトの制約によってRailsをRactorベースで実行できるようになるのは当分先のような気もしますが…
Building a Lightweight IR and Backend for YJIT
Railsの実行を実用として高速化することを目的に始まったYJITがバックエンドとしてRustを使うに至るまでの話。
元々x86_64のサーバ上で実行されることを前提にしていたためx86_64アセンブリを直接書いていたが、 AppleがmacをARMベースのM1プロセッサに変更したことによって、YJIT開発者が自身のマシン上でデバックできなくなってしまった。
YJITは従来、MRI Ruby VMの機械語であるiSeqを直接x86_64の機械語にしていたが、これを一旦YJITの中間表現に変換し、中間表現を実行マシンの機械語へコンパイルしている構造へ変更した。 中間表現の段階で各CPUアーキテクチャに合わせたOptimizeを行い、最終的にそれぞれの機械語へ変換している。
評価としては従来のJITバックエンドに比べるとまだ微小な性能劣化があるが、その代わりにARMに対応できたのは大きい。 性能を改善しつつ、RISC-V等の他CPUアーキテクチャへの対応も検討していきたい。
タイトルの通り、YJITをARMにも対応させるために導入した中間表現の話が主で、この発表においてはRustはenumの網羅性を保証するために便利だった、みたいなおまけ扱いでした。
Ruby、ひいてはRailsの実行が高速になるのはうれしいことなので期待してはいますが、 一方で今担当しているプロダクトはRailsをAWS ECSによってDockerコンテナとして起動しているので、 YJITが使えるようなRustコンパイラを含むRubyのDockerイメージが公式あるいはそれに近いものとして配布されるのか、あたりが気になるところです。
Towards Ruby 4 JIT
これまで、実行速度のためにx86_64に特化したYJITとCPUアーキテクチャを超えても動く互換性を重視してgccを間に挟む選択をしたMJIT、という棲み分けだったところから、 YJITがRustを使ってARMにも対応しようとしていることで、MJITはどうするのだろうという点に注目していました。
結論としては、RubyVM::MJITという名前空間で公然の秘密として提供されるJITフレームワークと、そのリファレンス実装という形になる模様。
これによって、JITの独自実装すら可能になるという、Rubyの有り余る柔軟性をさらに高めるヤバい(誉め言葉)代物になっていました。
Ruby debugger - The best investment for your productivity
debug gemをChromeのDeveloperConsoleやVSCodeと接続してデバッグする方法についてのデモ。
DeveloperConsoleやVSCodeのデバッガとやりとりするためのプロトコルがあって、それを実装すればよいらしい。
見るからに便利なのはわかるんですが、私のチームの場合普段開発しているのがJSON APIなraillsであることから基本的に rails server
でdevelopmentモードを立ち上げることはほとんどありません。
専らrspecでテストケースを書き、デバッグもrspecのテストケース指定で行っていることが多く、このdebug gemのデバッガ接続をrspecでやるにはどうすればいいのかがわかっておらず、まだ採用できていないというのが実情です。
そのあたりの方法を見つけられた暁にはまた記事としてまとめたいとは思っていますが…まだ見通しは立っていません。
Make RuboCop super fast
rubocopはlinterであるため開発中に何度も起動されるが、コマンドとして実行されるたびに全てのコードをrequireしていて起動に時間がかかっていたため、serverモードと呼ばれる、単一プロセスとして裏で待機しておくモードを追加した。
その際requireするコードも真に必要なものだけに絞ったことで従来比850倍の高速化をした。
rubocopには仕事の普段の開発でもよくお世話になっているほか、2度ほどコード自動修正に関するバグ報告をして対応していただいている3ので、引き続き何かあればバグ報告などやっていきたいと思います。
Method-based JIT compilation by transpiling to Julia
MJITやYJITはRubyの互換性を維持するためにメソッドの動的再定義ができる、などのRubyの特性を維持しているがために、常にメソッドが再定義されているかをチェックしなければならず、これが非常に大きなボトルネックとなっていて高速化に限界がある
PythonにはNumbaというJITコンパイラがあり、これはPythonの互換性を維持するモードと、メソッドの動的再定義可能性を捨てる代わりにより高速化したコードを生成するモードの2モードがある
Rubyにおいても、メソッドの動的再定義可能性を捨てる代わりに高速化したい需要はあり、その一つが科学技術計算に代表されるような大規模な数値計算である
PythonにおけるNumbaのようなJITを実現するにあたって、Rubyに文法が似ており、なおかつ高速な言語としてJuliaを使うことにした
RubyのASTをJuliaのASTにトランスパイルし、Juliaのコンパイラにコンパイルさせて高速化する
その際、JuliaとRubyのあいだでいくらか動作に差異があるので、そこを吸収するようなコードを書く必要があった
評価としては、少ない要素に大きな計算量を投じる必要がある際には大きく高速化したが、RubyオブジェクトをJuliaのオブジェクトに変換する処理がボトルネックで、要素が多い場合には逆に遅くなってしまう場合もあった
全体的に興味深い内容でした。 具体的にこれをどう実装したのかが気になるんですが、前述の新MJITをJITフレームワークとして使ったんでしょうか?
How fast really is Ruby 3.x?
Ruby3x3を合言葉として掲げ、Ruby2.x比で3倍の高速化を目指したRuby3系。
Railsではそれほどの高速化は果たせなかったといわれていますがそれはなぜなのか、また別のユースケースでは?
という主旨で、ログ収集ソフトウェアであるfluentdのコミッタであるfujimotosさんがfluentdのログ処理スループットという観点からの発表でした。
Railsの高速化がそれほどでもなかったのは、RailsはWebフレームワークであるので、データベースへのクエリの待ち時間など、I/Oが処理時間のほとんどを占めているから
一方、fluentdはログ収集・変形・送信集積基盤というソフトウェアの特性上、1行1行のログを大量に読んだそばから文字列オブジェクトとして処理しているため、Railsより高速化の恩恵を受けやすい
fluentdはtd-agentとしてRubyの実行ファイルも同梱したパッケージを出しているので、歴代のtd-agentの各バージョンを用いればRubyバージョンごとの高速化もある程度比較できるはず
結果、LTSVでの比較において、1.9.3が85720行/秒、2.1.10が104211行/秒のところ3.2.0+YJIT有効で268102行/秒だったので、1.9.3比で3倍強、2.1.10比で約2.5倍と確かに2.x比3倍速と言えなくもないくらいの高速化は果たしている
一方で、言語間の比較をするために、LTSVなファイルを読み込んで、ラベルをKey、値をValueであるような連想配列あるいはハッシュに格納するコードをRuby, Lua, Python, Perlで書き、その速度を比較した
結果、Ruby3+YJITでも素のLuaに勝てず、LuaJITに対しては2.5倍ほどの差を付けられてしまっている
ということで、Ruby、特に文字列処理周辺の高速化は今後も引き続き期待したい部分
Ruby3への移植はどうだったかというと、それほど苦労しなかった
出たバグもWindowsなどの環境依存に関するものがほとんどで、MRI側でバグ修正をしてもらった
Ruby3.2にすると、YJITが使える。 YJITはfluentdにとっては有効化するだけで10%ほど高速化するので素晴らしい
もう一つの目玉機能であるRactorは、Ractor間で共有できるオブジェクトに制限があるため、まだ導入できていない
以上のことから、fluentdとしては、Ruby3にアップデートYJITを有効化し、Ractorを活用できるように取り組みながら、引き続きRuby3の更なる高速化に期待している
ということでした。
td-agentをRubyバージョン間の比較に使うのはfluentdのコードの変更も影響するだろうと思うので厳密には微妙なのではないかと思いつつも、おおまかな性能変化を掴むレベルなら十分だったかと思います。
プログラマが楽に、柔軟に、短時間でコードを書けるために機能のデリバリまでの時間が短縮でき生産性が上がる、がRubyのコアコンセプトであると思っていますが、 それによる成果を更に倍増させるためにはやはり実行速度の高速化も欠かせないと思うので、引き続き期待したいところです。
Caching With MessagePack
Marshalはインスタンス変数なども含めRubyオブジェクトの全てをシリアライズするためあまりにも冗長でキャッシュには向かないからMessagePackを使え!といったような主旨。
Marshalがどのような表現でRubyオブジェクトをシリアライズしているか、MessagePackがどのように一般的なデータ型をシリアライズしているかを対比したあと、 MessagePackは一般的でないその他のデータ型もpacker(エンコーダ)とunpacker(デコーダ)を登録すればシリアライズできる、というのを実践していました。
タイトルこそ違いますが、先にRailsConf 2022で話していた下記のスライドとほぼ同じ内容だったように思います。
Shopifyが使っている、MessagePackをシリアライズ形式としているgemが下記。
error_highlight: user-friendly error diagnostics
Ruby 3.1において、NameError, NoMethodErrorが発生した場合に発生個所を取得できるようになったこと、それによってコンソール上で ^
を用いて下線のように示せるようになっていました。
Ruby3.2ではNoMethodErrorに加え、ArgumentErrorやTypeErrorも対象になったこと、Railsのエラー画面でも当該部分の背景色を変えて示されるようになるとのこと。
エラー発生個所として示すべき行数、文字数の決定方法としてASTと、それで絞り切れない部分には正規表現も使っているという話でした。
Syntax Tree
MRIが使っているRubyのparserは、非常に複雑で、どこかで文法エラーがあった場合に、そのエラーがあった箇所を捨ててそれ以外の解釈できた場所をとりあえず返す性質(error tolerant性)がない、などの問題を抱えている
parserを丸ごと再構築して、error tolerant性を備えたparserを実装し、それを用いてLangage Server Protocolサーバなどを実装して、Rubyのシンタックスハイライトや補完ができる可能性を拓きたい
といったような内容だったと理解しました。 parserについては大学院で本当にほんの少し基礎概念を触れた程度の知識しかありませんが、非常に野心的な取り組みだなと思います。
The Better RuboCop World to enjoy Ruby
rubocopはいつもお世話になっているが、数あるルールの中にはすべてのケースにおいて適用するべきというほどではないものも存在すると思う
Rubyを書きなれている人はルールを見て取捨選択ができるが、経験が少ないエンジニアはlinterからの指摘だからと適用するべきではないかもしれないものにまで常に従おうとしてしまう傾向がある
結果、無理にrubocopに従って逆に読みづらいコードが生成される場合がある
これはRubyをまだ書きなれていないエンジニアに、Rubyの自由さを感じてもらう機会を奪ってはいないだろうか?
rubocopから、そのルールの推奨度といったような概念を表現する手段を提供できないだろうか?
といったような問題提起、提案の発表でした。
私のチームは新しいルールはとりあえず有効化4しておいて、dependa_botからのPRでrubocopが新しいルールによる違反を検出したときにそのルールを採用するか考える、という方針にしています。
そのなかでつい先日、rubocop-rspec v2.13.0へのアップデートのうち、
RSpec/NoExpectationExample
に引っかかるテストケースがあり、それに対して採用するべきか無効化するべきか判断が分かれた、という事例がありました。
最終的には私の判断で無効化したのですが、この発表を聞いた直後にそういった事例があったために問題提起としてはある程度納得感のあるものだったと思います。
String Meets Encoding
String#encodeメソッドを高速化してほしいという話を勘違いしてCSV.read、ひいてはStinrg#splitを高速化した、という話。
上記ブログ記事によれば割と直前まで成果が出ず大変だったということですが、発表としては測定してボトルネックを見つけ、必要のない処理がされている箇所を見つけ、というステップがチュートリアルのようにきれいに書かれていたので、測定と改善の例としてはわかりやすかったんではないかと思います。
History of Japanese Ruby reference manual, and future
Rubyの日本語版リファレンスマニュアルについて。 実は、日本語版と英語版のリファレンスマニュアルがシステムごと分裂しているような状態になっていること自体を知りませんでした。
公判で触れられていた将来の展望についてはRD記法からmarkdownに移行する計画であるとか、Ruby on Wasmを活用したその場で実行可能なサンプルコードなど、実現したらどれも明らかに便利だろうな、と思うものばかりでした。
実際、引数を転送する記法が書かれていないなと気付きつつもRD記法がわからない、執筆体制がわからないのでどこからどうPRを送ればいいのかわからない、などで諦めたことがあるので、実現の暁には手が出せるところから貢献できたらと思います。
Matz Keynote コミュニティの力
前半はコミュニティの話。
Rubyは死んだ(毎年死んでいる)、Rubyは遅い、Rubyはpythonやphpに劣る、など、様々な"アドバイス"が相変わらず言われているが、それらとは関係なく、Rubyは現に価値を作り出している
Productivity: 極度に柔軟な言語機能が生産性を高めている
Community: 高い生産性を活用しさらに伸ばすコミュニティが形作られている
Joy: 極度に柔軟な言語機能を使って思った通りにプログラミングする楽しさ、言語機能を更に高めることによって感謝される嬉しさ
Money: そして何より、趣味レベルだけでなく、その極度に高められた生産性をフルに活用して企業が実際に、現にお金、価値を作り出している https://toprubycompanies.info/Rubyを作り始めたのは自分だが、今ではRubyに関するエコシステムはほぼ全て、コミュニティのContributeによって回っている
Contributeというと、Rubyそのものの開発、Rubyの開発にまつわる各種ツール、gemなどの開発といったようなコードを書くものが連想されがちではあるしかし、実際にはとてもたくさんのContributeの形がある
- ブログで記事を書く
- カンファレンスやミートアップ、勉強会を開催する、スポンサーする
- バグレポートする
- Ruby本体にfeatrue requestする
- バグを直す
- ドキュメントを書く、直す
- gemを作る
- 月1回のRuby本体の開発者会議で扱うfeature requestやbugのトリアージ、議事録作成
- ドキュメントの翻訳
- Ruby本体の開発者を、それらのフルタイムコミッタとして雇う
- など、他にも挙げ切れない数多くの形
こういった様々な貢献の形があるRubyコミュニティによって、Rubyは支えられている
まずはぜひ、RubyKaigiのレポート、感想ブログを書いてみてください
後半はRuby3.2で予定されている新機能の話
- Ruby on Wasm
- YJIT on Rust
- メモリアロケーション改善
- M:Nスレッド実行モデル
- もしかしたら間に合わないかも?
- イミュータブルなDataクラス
- https://github.com/ruby/syntax_suggest
- 文法の大きな変更はあまり考えておらず、しばらくrbsやLSPサーバーをはじめとしたツールチェインの改善を通しての開発者体験、生産性向上に注力していきたい
毎年死んでいるRubyはMatz Keynoteの鉄板ネタですね。
書き味が大幅に変わる文法の変更はしばらく考えていないということなので実行速度の向上をYJITなどに期待しつつ、Rubyコミュニティの一員としてのContributeを微力ながら続けていこうと改めて思いました。
まとめ
本記事では、三重県津市でオフライン開催されたRubyKaigi 2022に現地参加したことについてお話しました。
今回はMatzも松田さんもコミュニティに特に言及していたのが印象的でした。
僕個人としても、Rubyが好き、Rubyで仕事をしている、という人たちだけがいる空間が現実に顕現する、物理開催のRubyKaigiがついに帰ってきたというのはとても大きいことだったと思います。
物理的な厚みのある元同僚と年単位ぶりに会ったり、晩御飯を一緒に食べたり、今まで交友がなかった人とも新しく知り合い、仲良くなるなど、2018仙台, 2019福岡の盛大さとまではまだいかないにせよ、RubyKaigiの醍醐味が久方ぶりに得られたと感じました。
今回は僕だけで稟議を出して行って帰ってきてしまった5ので、次回の松本は興味がある同僚がいれば連れていきたいと思っています。
ブックウォーカーでは物理・電子・Web連載問わず漫画や本が好き、あるいはRubyに情熱を持っているWebアプリケーションエンジニアを募集しています。
興味がありましたらぜひ、採用情報ページからご応募ください。
- 元々は社内のコンフルエンスに書くつもりだったんですが、感想ブログの公開が推奨されたこと、社内だけに閉じておく理由が薄いなと思ったので出張の報告も兼ねてこのブログに書いています↩
- 転職活動をしたわけではなく、角川グループ内での転籍なので前職と言っていいのかは悩ましいですが↩
- Rubocop Style/RedundantSelf changes the semantics of breakouted method arguments when autocorrecting, Style/ClassAndModuleChildren auto correction makes invalid code↩
-
NewCops: enable
↩ - 津に向けて出発してから誰か連れてくればよかった、と思い至った↩