やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

Rubyでパフォーマンス(メモリ使用量・実行速度)を計測する

先日、Rails製のアプリケーションのパフォーマンス検証をする機会がありました。
計測したいのはメモリ使用量と実行時間の2つです。後者の実行時間に関してはRubyが標準ライブラリとして提供しているbenchmarkを使えば事足りますが、前者のメモリ使用量となるとRubyでは計測する方法が限られてきます。

どうしたものかな...と調べていたところ、以下の記事を発見しました。

nishinatoshiharu.com

Rubyでメモリ使用量を計測する2つの方法が紹介されています。

  • ObjectSpace.memsize_of_all: Rubyオブジェクトが使用するメモリの総量
  • rss: OSの1プロセス(Rubyを実行している)が使用するメモリの送料

この計測方法と実行時間の計測をまとめて便利に計測を行えるクラスを作成したので、共有したいと思います。

パフォーマンス計測のクラス

元々は単一の関数として定義しようかと思いましたが、最終的にクラスになりました。
このクラスを貼り付ければ、全ての準備が完了するようにしてあります。外部ライブラリは使用していないので、Rubyがインストールされていれば実行可能です。

class Messurement
  def initialize
    require 'benchmark'
    require 'objspace'

    @benchmark_caption = Benchmark::CAPTION
    @MESSURE_RESULT = Struct.new(
      'MessurementResult',
      :label,
      :before_moa,
      :before_rss,
      :after_moa,
      :after_rss,
      :benchmark,
    )
  end

  def exec(label, &block)
    raise 'Block not specified' unless block

    before_moa, before_rss = memory_values()
    benchmark_result = Benchmark.measure { yield }
    after_moa, after_rss = memory_values()

    @MESSURE_RESULT.new(
      label,
      before_moa,
      before_rss,
      after_moa,
      after_rss,
      benchmark_result,
    )
  end

  def print_exec(label, &block)
    result = exec(label, &block)

    puts "Result of 「#{label}"
    puts "[BEFORE] memsize_of_all: #{result.before_moa} MB, rss: #{result.before_rss} MB"
    puts "[AFTER] memsize_of_all: #{result.after_moa} MB, rss: #{result.after_rss} MB"
    puts '-Benchmark Result------'
    puts @benchmark_caption
    puts result.benchmark
    puts '-----------------------'
  end

  private

  def memory_values
    memsize_of_all = (ObjectSpace.memsize_of_all * 0.001 * 0.001).round(2)
    rss = (`ps -o rss= -p #{Process.pid}`.to_i * 0.001).round(2)

    [memsize_of_all, rss]
  end
end

使い方としては、Messurementクラスのインスタンスを作成後、exec関数かexec_print関数を呼び出すのみです。
exec関数は計測結果を構造体として返しますが、exec_print関数は結果を標準出力するようにしました。
構造体には実行前のメモリ使用量(memsize_of_all, rss)と実行後のメモリ使用量。benchmarkで計測した実行結果が含まれています。

サンプル

exec

messure = Messurement.new
messure.exec '1 + 1' do
  1 + 1
end

#<struct Struct::MessurementResult
 label="1 + 1",
 before_moa=4.12,
 before_rss=20.83,
 after_moa=4.13,
 after_rss=20.83,
 benchmark=
  #<Benchmark::Tms:0x00000001263ceb10
   @cstime=0.0,
   @cutime=0.0,
   @label="",
   @real=4.999339580535889e-06,
   @stime=5.999999999950489e-06,
   @total=1.6999999999933735e-05,
   @utime=1.0999999999983245e-05>>

exec_print

messure = Messurement.new
messure.print_exec '1 + 1' do
  1 + 1
end

Result of 「1 + 1」
[BEFORE] memsize_of_all: 3.6 MB, rss: 18.64 MB
[AFTER] memsize_of_all: 3.6 MB, rss: 18.66 MB
-Benchmark Result------
      user     system      total        real
  0.000017   0.000006   0.000023 (  0.000006)
-----------------------

ブロックを指定して計測したい処理を受け渡すようにしたのは完全にRspecを意識しています。
ObjectSpaceたるモジュールについて全く知らなかったので勉強になりましたし、他にパフォーマンスを計測できる方法や取得可能な値がないか、読み込んでみようと思います。

少しでも「ええな〜」と思ったらはてなスター・はてなブックマーク・シェアを頂けると励みになります。