Rubyのプロファイラメモ

プロファイラを活用することで、プログラムの実行速度や使用リソースの収集ができます。そして、プロファイル結果を解析することでコードのボトルネックを把握し、プログラムのパフォーマンスチューニングを効率的に実施できます。

プロファイラとは何か

プロファイラとは、プログラムの実行時にパフォーマンス情報を収集するツールです。プログラムの各部分の実行時間や、使用リソースなどを詳細に解析できます。プロファイラを使用することで、パフォーマンス上のボトルネックを特定し、最適化を行うことができます。

プロファイラには主に、実行速度の最適化とリソース使用量の最適化のケースで利用されます。

  • 実行速度の最適化では、プログラムの各部分がどの程度の時間を要しているかを確認することで、チューニングが必要な箇所を特定します。
  • リソース使用量の最適化では、オブジェクトの割り当てやメモリ使用量など、リソースの利用状況を分析し、リソース使用量の最適化をはかることができます。

Rubyのプロファイリングをするgem

Rubyの主要なプロファイリングを行うgemにはいくつかあります。

名前 star (23/4) 説明
ruby-prof 2k Rubyプログラムの実行速度をプロファイルする高速なコードプロファイラ。
多様なレポート形式や、グラフ表示が可能
stackprof 2k サンプリングしたコールスタックのプロファイラ。
rack-mini-profiler 3.5k HTMLにプロファイル結果を表示するプロファイラ

他にも、メモリ消費やガベージコレクタの動作を詳細に分析できる memory_profilerRSpecやFactoryBotなどテストをプロファイルできる test-prof などがあります。

ruby-profの使い方

gemのインストール

gem 'ruby-prof'

Rackアプリケーションへの設定方法

require 'ruby-prof'

# 計測方法(経過時間、プロセス時間、オブジェクト割り当て、メモリを測定できる)
RubyProf.measure_mode = RubyProf::WALL_TIME 

# Rackアプリケーションで特定のパスをプロファイル
use Rack::RubyProf, path: 'tmp/ruby-prof', only_paths: [%r{/api/v1/users}]

レポート形式はテキスト、HTML、グラフなどいろいろある。

ruby-profの結果のHTML例

詳細の使い方は ruby-prof を確認

stackprofの使い方

gemのインストール

gem 'stackprof'

Rackアプリケーションへの設定方法

require 'stackprof'
mode = :wall # cpu, object, custom などできる
use StackProf::Middleware,
  enabled: true,
  mode:,
  interval: 1000,
  save_every: 5,
  # raw: true,
  path: "tmp/stackprof-#{mode}-myapp.dump"

dumpファイルをstackprofコマンドで解析する

stackprof tmp/stackprof-cpu-*.dump --text --limit 10
==================================
  Mode: cpu(1000)
  Samples: 17 (34.62% miss rate)
  GC: 1 (5.88%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
         7  (41.2%)           7  (41.2%)     MyApp#do_slow_stuff
         3  (17.6%)           3  (17.6%)     IO#set_encoding
         6  (35.3%)           3  (17.6%)     Kernel#require
         1   (5.9%)           1   (5.9%)     (marking)
         1   (5.9%)           1   (5.9%)     Mysql2::Client#connect
         1   (5.9%)           1   (5.9%)     Mysql2::Client#_query
         1   (5.9%)           1   (5.9%)     Hash#delete
        16  (94.1%)           0   (0.0%)     Sinatra::Base#invoke
        16  (94.1%)           0   (0.0%)     Sinatra::Base#call!
        16  (94.1%)           0   (0.0%)     Sinatra::Base#call

コードのどの部分が遅いかもみれる

stackprof tmp/stackprof-wall-*.dump --text -m --file app.rb 

                                  |   103  |   get '/api/v1/users' do
   10    (4.0%)                   |   104  |     users = select_users
                                  |   105  | 
  233   (92.1%)                   |   106  |     do_slow_stuff
                                  |   107  | 
    6    (2.4%)                   |   108  |     json(status: true, data: { users: })
                                  |   109  |   end
                                  |   110  | 
                                  |   111  |   def select_users
                                  |   112  |     users = []
                                  |   113  | 
   10    (4.0%)                   |   114  |     mysql_db.query('SELECT id, name FROM users').each do |row|
                                  |   115  |       users.push({
                                  |   116  |         id: row[:id],
                                  |   117  |         name: row[:name],
                                  |   118  |       })
                                  |   119  |     end
                                  |   120  |   end
                                  |   121  | 
                                  |   122  |   def do_slow_stuff
  233   (92.1%)                   |   123  |     1_000_000.times.each do |n|
                                  |   124  |       n + 1
  233   (92.1%) /   233  (92.1%)  |   125  |     end

詳細な使い方は GitHub - tmm1/stackprof: a sampling call-stack profiler for ruby 2.2+ を確認

rack-mini-profilerの特徴

gemのインストール

gem 'rack-mini-profiler'

Rackアプリケーションに設定

require 'rack-mini-profiler'
use Rack::MiniProfiler

HTMLの画面に表示される

rack-mini-profilerの出力例

参考: プロファイル結果の出力例の対象コード

上記のプロファイルの出力結果のコード

get '/api/v1/users' do
  users = select_users

  do_slow_stuff

  json(status: true, data: { users: })
end

def select_users
  users = []

  mysql_db.query('SELECT id, name FROM users').each do |row|
    users.push({
      id: row[:id],
      name: row[:name],
    })
  end
end

def do_slow_stuff
  1_000_000.times.each do |n|
    n + 1
  end
end