nipeblog

Rubyのプロファイラメモ

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

プロファイラとは何か

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

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

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

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

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

名前

Star数 (23/4時点)

説明

ruby-prof

2k

Rubyプログラムの実行速度をプロファイルする高速なコードプロファイラ。
多様なレポート形式や、グラフ表示が可能

stackprof

2k

サンプリングしたコールスタックのプロファイラ

rack-mini-profiler

3.5k

HTMLにプロファイル結果を表示するプロファイラ

他にも、メモリ消費やガベージコレクタの動作を詳細に分析できる memory_profiler やRSpecや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

詳細な使い方は tmm1/stackprof を確認してください。

rack-mini-profilerの使い方

gemのインストール

gem 'rack-mini-profiler'

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

require 'rack-mini-profiler'

use Rack::MiniProfiler

HTMLの画面を開くとプロファイル結果が表示される

rack-mini-profilerの出力例

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

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

# sinatraで書いています

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