Vernier: A next generation profiler for CRuby
こんにちは、2024年に新卒で入社し、ジョブハウスでバックエンドエンジニアをしているnozomemeinです。
本記事では、1日目のJohn Hawthornさんによるセッション、Vernier: A next generation profiler for CRuby
について紹介させていただきます。
Vernierとは何か
Vernierは、Rubyのパフォーマンスプロファイリングを行うためツールです。
従来のプロファイラーでは、GVL(Global VM Lock)の影響を受けやすく、正確なデータを得るのが難しいという課題がありました。
また、既存の有名なprofilerであるstackprofも認知度こそあれど、10年以上前に開発されたもので、
Rubyのアップデートに追従できているとはいえない状況でした。
しかし、Vernierはこの問題を解決し、GVLの動きを含めてプロファイリングすることができます。
これにより、Rubyスレッドの実行状態を詳細に追跡し、パフォーマンスのボトルネックを正確に特定することが可能です。
Vernierの主な機能
Vernierは下記のような特徴を持ちます。
- GVLの影響を受けないプロファイリング:VernierはGVLの影響を受けず、Rubyスレッドの実行状態(Running、Stalled、Suspended)を正確に記録できる。
- サンプル精度の向上:Profilerで起こりがちなSafe point biasの問題を回避し、プロファイリングの精度を向上。
- パフォーマンスへの影響が少ない:Vernierは専用のスレッドを利用してサンプリングを行うため、アプリケーションのパフォーマンスに与える影響を最小限に抑えます。
Vernierの使い方
Vernierの使用は非常に簡単です。以下はCLIから呼び出す方法と、Rubyコード内でインラインで呼び出す方法の例です。
CLIからの使用方法
$ gem install vernier
$ vernier run -- ruby your_script.rb
このコマンドでプロファイラーを起動し、結果は圧縮済みJSONファイル(xxx.json.gz
)として出力されます。
このファイルは、vernier.profで閲覧可能です。 リンク先のvernier.profはFirefox ProfilerをベースにしたWebアプリだそうです。
実際に$ vernier run -- bundle update
を行うと、横軸時間でどの部分で処理に時間がかかっているのかを可視化することができます。
また、黄色い部分がRubyのC function(C実装)箇所で、灰色がRuby実装箇所で表示されています。
各々のメソッドがRubyないしはCのどちらで実装されているかがひと目でわかるのは、Rubyの言語処理系の実装を把握する上で便利ですね。
call stackから、String#split
のようなメソッドの、呼び出し元を確認できます(Call TreeのタブでInvert call stack
にチェックを入れると、call stackの深い階層から順に見やすく表示されます。)
GCのmark&sweepの挙動やThreadの実行状態も追跡できるようです。
Rubyコード内での使用方法
上の例ではCLI実行の方法を示していましたが、Rubyコード内でインラインでVernierを呼び出すこともできます。
以下はその例です。
require 'vernier' Vernier.profile do # プロファイルしたいコード your_method end
この方法を使えば、特定のコードブロックのみをプロファイルすることも可能です。
簡単な実演
簡単な実演として、スレッドを3つ立てて、それぞれのスレッドの状態をプロファイルするサンプルコードを紹介します。
require 'vernier' require 'net/http' Vernier.profile(out: 'time_profile.json') do threads = [] threads << Thread.new do # Thread 1 puts 'Thread 1: Starting I/O operation...' Net::HTTP.get('example.com', '/') puts 'Thread 1: I/O operation complete' end threads << Thread.new do # Thread 2 10.times do |i| puts "Thread 2: #{i}" sleep(0.5) end end threads << Thread.new do # Thread 3 10.times do |i| puts "Thread 3: #{i}" end end threads.each(&:join) end puts 'Profiling complete'
このコードを実行すると、Vernierは各スレッドの状態を記録し、結果を詳細に分析できます。 各ThreadのGVLが実際にswitch(Running, Stalled/Suspendedが競合しない)されている様子が確認できます。
Thread1のMaker Chart
Thread2のMaker Chart
Thread3のMaker Chart
Vernierがもたらす利点
Vernierの最大の利点は、GVLの影響を受けずに正確なプロファイリングデータを得られることです。これにより、Rubyアプリケーションのパフォーマンスボトルネックを正確に特定し、効率的な最適化が可能になります。また、Safe point biasがないため、プロファイリングデータの信頼性も向上しています。
GVLの影響を受けないプロファイリング
従来のプロファイラーはGVLの影響を受けやすく、特にスレッドの実行状態を正確に追跡するのが難しいことがありました。GVL(Global VM Lock)は、同時に実行できるスレッドを1つに制限するため、パフォーマンス計測時にGVLがプロファイラーの計測を止めてしまうという課題がありました。しかし、VernierはGVLの影響を受けずにプロファイルが取れるため、Rubyスレッドの実行状態(Running、Stalled、Suspended)を正確に記録できます。これにより、マルチスレッドアプリケーションのボトルネックをより効果的に特定できるようになります。
サンプル精度の向上
従来のプロファイラーでは、Safe point biasの問題がありました。Safe point biasとは、プロファイラーがスレッドの状態を観察する際に、観察タイミングがSafe pointにバイアスされることで、実際の実行状況が正確に反映されないことを指します。Vernierはこの問題を回避する設計となっており、プロファイリングのサンプル精度が向上しています。Vernierは専用のスレッドを利用してサンプリングを行い、Safe pointに依存しない観察が可能です。
パフォーマンスへの影響が少ない
Vernierは、専用のスレッドを使用してサンプリングを行うため、アプリケーションのパフォーマンスに対する影響を最小限に抑えます。これにより、実際の稼働環境でも安心して使用することができます。さらに、VernierはRactorにも対応しており、複数のRactorを利用した並行処理をプロファイルする際にも有効です。
感想
新卒として入社してすぐに参加したRubyKaigiでしたが、周りの皆さんとのレベル差にただただ圧倒される1日でした。 この壁を乗り越えてこそ、より一層RubyKaigiが楽しくなるのだろうと、わくわくさせられるような1日でもありました。
RubyKaigi 2024の1日目には、同じくRubyのプロファイラーに関するセッションとしてThe depths of profiling Rubyもありました。このセッションでは、高機能なプロファイラーがいかにしてプログラムの実行を正確に追跡し、洗練された可視化を提供するか、そしてパフォーマンスへの影響を最小限に抑えるかについて詳しく説明されていました。Rubyプロファイラーに求められる高度な機能として、RubyレベルとCレベルのスタックを統合したり、GCやGVLのイベントを記録することが挙げられます。
さらに、2日目にはOptimizing Ruby: Building an Always-On Production Profilerというプロファイラー関連の講演が予定されています。こちらもぜひ参加してみたいと思います。
最後にRuby Official PartyでJohn Hawthornさんとお話しする機会をいただき、ツーショット写真も撮らせていただいたので、記念に掲載させてください。
Techouseでは、社会課題の解決に一緒に取り組むエンジニアを募集しております。 ご応募お待ちしております。 jp.techouse.com