こんにちは、Wantedly でエンジニアをしている南です。 そろそろクリスマスです。Ruby の新しいバージョンがリリースされる時期ですね!!
という事で、来たる Ruby 2.5 に向けてバージョンアップの意欲を高める為に、 Ruby のバージョンを上げるメリットについて、過去にバージョンアップした時のことを振り返りながら少し考えてみたいと思います。
以前、Wantedly は Rails アプリケーションの Ruby のバージョンを 2.3 から 2.4 に上げました。その結果、平均 response time が 29% 高速化 し、パフォーマンスが劇的に改善しました。当時はある程度の高速化は期待していましたが、ここまで劇的な変化は予想しておらず、驚きでした。
その変化については考察して社内で共有したのですが、せっかくなのでブログにもまとめておきたいと思います。
TL; DR Wantedly では以前、一番歴史の長い巨大な Rails アプリケーションの「Ruby のバージョン」を、2.3 から 2.4 に上げました。その結果、 平均 response time が 188ms から 146ms へ 29% 高速化 し、パフォーマンスが劇的に改善しました。 実際に NewRelic で確認した変化 は以下です。特に違いが大きかった CategoryTagsController#index というエンドポイントでの変化を載せたのですが、2.4へのバージョンアップを境にハッキリと高速化しているのが分かると思います。 Ruby 2.4 で行われた高速化の中で、特に影響が大きいのは 「ハッシュテーブルの高速化」 と思われます。その影響についてまとめました。 バージョンアップには機能強化、パフォーマンス改善などメリットがたくさんあります。勿論、依存 gem のバージョンアップなど多少の労力は必要になりますが、積極的にバージョンアップする事でメリットを享受できるはずです。 Ruby 2.5 がリリースされるのはもうすぐです! 背景 Wantedly はマイクロサービス化を進めており、様々な言語で実装されたサーバーやジョブシステムが動いています。パフォーマンスを求められるところではGoやC++を、機械学習の training を行ったりその結果をWeb APIとして返したりするサーバーにはPythonを利用しており、用途に合わせて適切な環境を整え、またある程度小さいサービスに分割することで継続的な変更を行いやすくしています。
それらのサービスの中でも、最も歴史が長く大きな Rails アプリケーションが、このブログが掲載されている「Wantedly Feed」や会社の募集を見て話を聞きに行く「Wantedly Visit」などがのっている「Wantedly App サービス」です。
この「Wantedly App サービス」は定期的に Rails や Ruby のバージョンを上げていて、今は Rails 5 が Ruby 2.4 上で動いています。バージョンアップする理由はいくつかありますが、一番の理由は「コミュニティの進化に継続的について行くことで、高速化や機能追加などの恩恵を受けられる様にする事」です。特に Rails は非互換な変更が多い為、多少の痛みは伴いますが、バージョンアップによる恩恵も十分にあると考えています。
以前、「Wantedly App サービス」をRuby 2.4 にバージョンアップした際は、想像以上に高速化の恩恵を受けました。もうすぐ 2.5 がリリースされる今、「Wantedly App サービス」を Ruby 2.4 に上げた頃を振り返る事で、バージョンアップのメリットについて実例と共に考えてみたいと思います。
尚、Wantedly はバージョンアップなどは積極的にやりやすい環境にあると思います。その理由としては、マイクロサービス化によって1つ1つのサービスが過剰にファットなサービスになっていない事、またデプロイシステムに Docker を利用している為に、「新しいバージョンの Ruby が動く Docker image をためしに作ってステージング環境へ deploy する」といった事が簡単に出来る様になっている事などが挙げられます。
Ruby 2.4 に上げた結果 Ruby のバージョンを上げた際は、上述の通り パフォーマンスが大きく改善(平均 response time で見て 188ms -> 146ms へ29%高速化) しました。元々パフォーマンス改善を強く期待していた訳ではない為、これは嬉しい驚きでした。
エンドポイントごとの変化を見てみると、どのエンドポイントも全体として高速化していました。処理ごとに分けて見てみると、 「view (haml) の処理」 や 「action の中の処理」 が高速化している事が分かりました。
view (haml) は元々は template ファイルですが、実行時は compile されて ruby のメソッド呼び出しになります。また、action の中では model の処理など Ruby のメソッド呼び出しを行っています。結果的に、 「Ruby で実行される部分の処理が確かに高速化した」 事が分かりました。
[#index アクション] 131ms -> 100ms (31%高速化) [view (haml)] 161ms -> 135ms (19%高速化) 考察 では、Ruby 2.4 に上げるだけで高速した要因は何なのでしょうか?
答えはもちろん、Ruby 2.4 が以前のバージョンよりも早くなったからです。では、Ruby はなぜ早くなったのでしょうか?また、どう変わったのでしょうか?
Ruby 2.4 で行われたパフォーマンス改善のうち、主要なものとして公表されているのは以下の4つです。 cf. https://www.ruby-lang.org/ja/news/2016/12/25/ruby-2-4-0-released/
Ruby 内部のハッシュテーブル実装の高速化 Array#max, Array#min の効率化 Regexp#match? の高速化 インスタンス変数アクセスの高速化 その中でも、特に全体的なパフォーマンス改善に寄与していると思われるのが、1の 「Ruby 内部のハッシュテーブル実装の高速化」 です。
これについて、もう少し詳しく見てみましょう。
Ruby 内部のハッシュテーブル実装の高速化 Ruby 2.4 では、ハッシュテーブルの実装方法が「チェイン法(Chaining)」と呼ばれる方法から「オープンアドレス法(Open Addressing)」と呼ばれる方法に変わりました。これによって、CPU の内部キャッシュヒット率が上昇し、高速化したと言われています。
ソースコード中のコメントによれば、マイクロベンチで 40% 高速化している と主張されており、また RubyBench などの結果を見ていると変化の大きいものでは 123%高速化 という結果が出ています。2倍以上高速化しているわけですから驚きですね! cf. https://bugs.ruby-lang.org/issues/12142 cf. https://rubybench.org/ruby/ruby/releases?result_type=hash_small8
尚、「なぜオープンアドレス法で早くなるのか」については、自分が昔簡単にまとめたスライドがあるので、もう少し詳細が知りたい方はそちらも参照してみてください。
提案・実装をしたご本人である Vladimir Makarov さん による解説記事もありますので、こちらも参照してみてください。
高速化の対象となったのは Ruby 内で st_table
という名前で定義されているハッシュテーブルで、これはHashクラスの実装をはじめとして、インスタンス変数やスレッドローカル変数、環境変数などその他様々な機能の内部実装に使われています。 (興味がある人は ruby のコードに対して st_table
で grep してみてください。至る所で使われているのが分かると思います。ちなみに、何故か constant や method 探索の際に rb_id_table
という別のデータ構造が使われています。 cf. https://github.com/ruby/ruby )
つまり、Ruby 2.4 では、 広く使われているデータ構造に対して、大幅な改善が行われている わけです 。 そのために、NewRelic で計測したデータでも顕著に改善が見られるほど、大きな改善となったのだと思われます。
まとめ もうすぐ Ruby 2.5 がリリースされます! Ruby のバージョンを 2.3 から 2.4 に上げた際は、 NewRelicで見て29%パフォーマンスが改善 しました。Ruby はバージョンが上がる度に便利になるだけでなく、パフォーマンスもガンガン改善しています。 皆さんも積極的に Ruby のバージョンを上げて、その恩恵を受けましょう!
余談 ハッシュテーブル (st_table)
の最適化を提案・実装した Vladimir Makarov さん は、 Red Hat で GCC の開発に携わっている凄腕エンジニアです。 彼は st_table
で Ruby 2.4 をガツンと早くしただけでなく、 MJITというMRubyに載せるJITを提案、実装 しています。今年のRubyKaigiで最終日にKeynoteをされていたので、記憶に新しい方も多いのではないでしょうか。Ruby 3x3 に向けて、今後も彼によるRuby の更なる高速化が期待出来そうですね!
余談2 Ruby 2.5 でもいくつかの高速化が行われています。 watson さんという方が精力的に高速化 patch を送っていたのですが、僕自身も少しだけ高速化に貢献しました(マイクロベンチで 70% 高速化、haml のベンチで5%くらい高速化しました)
Ruby 2.5 における主要な高速化については、自分が簡単にまとめたスライドがあります。こちらも参照してみてください。
Ruby 2.5 ももうすぐリリースされます。またどれだけパフォーマンスが改善されるか試してみたいですね!