Rubyのメモリ使用量の計測をmemsize_of_allとpsコマンドで行う

Ruby

Rubyの実装で利用されるメモリを計測する方法には主に以下の2つがあります。

Rubyにおけるメモリ使用量の計測方法
  • psコマンドで実行プロセスのrss(物理メモリ使用量)を計測する
  • ObjectSpaceモジュールでRubyオブジェクトのメモリ使用量を計測する

今回は上記2つの具体的な利用方法について紹介します。

なおObjectSpaceモジュールには『任意のオブジェクトのメモリを使用量を計測するmemsize_of』や『ブロック内に囲まれたオブジェクトをトレースするtrace_object_allocations』がありますが、今回は『Rubyオブジェクト全体のメモリ使用量を計測するmemsize_of_all』を利用した方法について紹介します。

メモリ使用量の計測方法

『メモリ使用量を計測 → 計測対象のロジックを実行 → メモリ使用量を計測』とすることで、ロジック内でどれだけメモリが利用されたかわかります。
たとえば、以下のようにすることでBook.all.eachというブロックで利用されるメモリが計測できます。

Model.all.eachはあくまでサンプルです。Model.all.eachはデータをすべてメモリにのせてしまうアンチパターンなので使用しないようにしましょう。

lib/tasks/batch.rake

namespace :batch do
  desc "example"
  task example: :environment do
    require 'objspace'
    # ObjectSpaceはデフォルトがバイト表示
    # psのrssはデフォルトがキロバイト表示
    # Process.pidはカレントプロセスのプロセスID
    puts "[BEFORE] memsize_of_all: #{(ObjectSpace.memsize_of_all * 0.001 * 0.001).round(2)} MB, rss: #{(`ps -o rss= -p #{Process.pid}`.to_i * 0.001).round(2)} MB"

    Book.all.each do |book|
      book.price += 50
      book.save!
    end
    puts "[AFTER] memsize_of_all: #{(ObjectSpace.memsize_of_all * 0.001 * 0.001).round(2)} MB, rss: #{(`ps -o rss= -p #{Process.pid}`.to_i * 0.001).round(2)} MB"
  end
end

実行結果

> rails batch:example

[BEFORE] memsize_of_all: 20.31 MB, rss: 75.57 MB
[AFTER] memsize_of_all: 30.5 MB, rss: 89.55 MB

以下のようにロジックの途中でメモリ使用量を出力することで、メモリ使用量の変化が確認できます。

lib/tasks/batch.rake

namespace :batch do
  desc "example"
  task example: :environment do
    require 'objspace'
    puts "[BEFORE] memsize_of_all: #{(ObjectSpace.memsize_of_all * 0.001 * 0.001).round(2)} MB, rss: #{(`ps -o rss= -p #{Process.pid}`.to_i * 0.001).round(2)} MB"
    Book.all.each_with_index do |book, n|
      book.price += 50
      book.save!

      # 100回ごとに出力する
      if n % 100 == 0
        puts "[Book #{n}] memsize_of_all: #{(ObjectSpace.memsize_of_all * 0.001 * 0.001).round(2)} MB, rss: #{(`ps -o rss= -p #{Process.pid}`.to_i * 0.001).round(2)} MB"
      end

    end
    puts "[AFTER] memsize_of_all: #{(ObjectSpace.memsize_of_all * 0.001 * 0.001).round(2)} MB, rss: #{(`ps -o rss= -p #{Process.pid}`.to_i * 0.001).round(2)} MB"
  end
end

実行結果

> rails batch:example

[BEFORE] memsize_of_all: 20.33 MB, rss: 75.47 MB
[Book 0] memsize_of_all: 22.6 MB, rss: 81.27 MB
[Book 100] memsize_of_all: 25.99 MB, rss: 88.91 MB
[Book 200] memsize_of_all: 23.75 MB, rss: 89.16 MB
[Book 300] memsize_of_all: 32.49 MB, rss: 89.23 MB
[Book 400] memsize_of_all: 24.38 MB, rss: 89.24 MB
[Book 500] memsize_of_all: 27.09 MB, rss: 89.32 MB
[Book 600] memsize_of_all: 31.13 MB, rss: 89.34 MB
[Book 700] memsize_of_all: 29.55 MB, rss: 89.5 MB
[Book 800] memsize_of_all: 30.12 MB, rss: 89.52 MB
[Book 900] memsize_of_all: 26.84 MB, rss: 89.54 MB
[AFTER] memsize_of_all: 31.66 MB, rss: 89.58 MB

参考: メモリ使用量の計測をメソッド化する

Processing large CSV files with Rubyではyieldを利用してメモリ使用量の計測をメソッド化しています。
参考としてメモリ使用量をメソッド化したソースコードを紹介します。

lib/tasks/batch.rake

namespace :batch do
  desc "example"
  task example: :environment do
    print_memory_usage do
      Book.all.each do |book|
        book.price += 50
        book.save!
      end
    end
  end

  def print_memory_usage
    require 'objspace'
    memsize_before = ObjectSpace.memsize_of_all * 0.001 * 0.001
    rss_before = `ps -o rss= -p #{Process.pid}`.to_i * 0.001
    yield
    memsize_after = ObjectSpace.memsize_of_all * 0.001 * 0.001
    rss_after = `ps -o rss= -p #{Process.pid}`.to_i * 0.001
    puts "memsize_of_all: #{(memsize_after - memsize_before).round(2)} MB, rss: #{(rss_after - rss_before).round(2)} MB"
  end
end

実行結果

> rails batch:example

memsize_of_all: 11.31 MB, rss: 14.02 MB

さらにBenchmark.realtimeを利用すれば実行時間も計測できます。

namespace :batch do
  desc "example"
  task example: :environment do
    print_memory_usage do
      print_time_spent do
        Book.all.each do |book|
          book.price += 50
          book.save!
        end
      end
    end
  end

  def print_memory_usage
    require 'objspace'
    memsize_before = ObjectSpace.memsize_of_all * 0.001 * 0.001
    rss_before = `ps -o rss= -p #{Process.pid}`.to_i * 0.001
    yield
    memsize_after = ObjectSpace.memsize_of_all * 0.001 * 0.001
    rss_after = `ps -o rss= -p #{Process.pid}`.to_i * 0.001
    puts "memsize_of_all: #{(memsize_after - memsize_before).round(2)} MB, rss: #{(rss_after - rss_before).round(2)} MB"
  end

  def print_time_spent
    time = Benchmark.realtime do
      yield
    end
    puts "Time: #{time.round(2)} secs"
  end
end

実行結果

> rails batch:example

Time: 10.83 secs
memsize_of_all: 10.96 MB, rss: 14.21 MB

まとめ

Rubyにおけるメモリ使用量の計測方法
  • 『ObjectSpace.memsize_of_all』でRubyオブジェクトのメモリ使用量を確認できる
  • 『ps -o rss= -p #{Process.pid}』でカレントプロセス(Rubyアプリケーション)の物理メモリ使用量を確認できる
  • ロジック前後のObjectSpaceやrssを比較することで、ロジックで利用されたメモリを計測できる

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!

参考