メモリプロファイラ
メモリプロファイラを作りたいなと思い、gc.cを眺めていたら、ObjectSpace#count_objectsなるメソッドを見つけました。
irb(main):001:0> ObjectSpace.count_objects => {:TOTAL=>28000, :FREE=>9767, :T_OBJECT=>105, :T_CLASS=>745, :T_ICLASS=>28, :T _MODULE=>26, :T_FLOAT=>5, :T_STRING=>4866, :T_REGEXP=>87, :T_ARRAY=>1044, :T_HAS H=>112, :T_BIGNUM=>3, :T_FILE=>7, :T_DATA=>532, :T_MATCH=>92, :T_VALUES=>136, :T _NODE=>10445} irb(main):002:0>
これを使ってメモリプロファイラもどきを作ってみました。
使い方です
- ruby 1.9.0でかなり新しいバージョンじゃないと動かないと思います
- rgplot(http://rubyforge.org/projects/rgplot)が必要なのでインストールしておきます
- 最後に示すソースコードをmemprof.rbという名前でセーブします
- ruby memprof プロファイルしたいプログラム 引数 として起動します。
- カレントディレクトリにfoo.pngというグラフが出来ています。
- WATCH_TYPEを変えることでどの型のデータを表示するか変えることが出来ます。
例えば、次のようなプログラムのプロファイルを取ってみます。
a = [] (1..1000000).each do |n| a.push n.to_s end (1..1000000).each do |n| a[n] = n if n % 100000 == 0 then ObjectSpace.garbage_collect end end
途中でGCを強制的に起しています。そうしないとオブジェクトが減らないのでグラフが面白くないです。
こんな感じのグラフになります。ここで注意することは、型ごとの総バイト数じゃなくてその型のオブジェクトの数になっていることです。だから、配列はものすごく大きくなりますが、数は増えてないのでグラフには表れないです。
memprof.rbはこんな感じで動きます。
- メモリプロファイル用のスレッドを用意し、一定時間間隔でObjectSpace.count_objectsを実行します
- 実行した結果は配列に実行した時間とともに記録しておきます
- Ruby終了時にGnuplotのグラフに出力します(END{}を使います)
#!/bin/env ruby # # メモリプロファイラ # require 'gnuplot' module MemProf PROFILE_RESOLUTION = 0.01 WATCH_TYPE = [ :TOTAL, :T_ARRAY, :T_STRING, :T_NODE ] TEXT_OUTPUT = false GNUPLOT_OUTPUT = true GNPULOT_FORMAT = "png" GNUPLOT_OUTFILE = "foo.png" mem_snapshot = [] Thread.new(mem_snapshot) do |ms| prevtime = 0 while true do currenttime = Process.times.utime if currenttime - prevtime > PROFILE_RESOLUTION then ms.push [currenttime, ObjectSpace.count_objects] end prevtime = currenttime Thread.pass end end END { if TEXT_OUTPUT == true then WATCH_TYPE.each do |ev| print "# #{ev} \n" mem_snapshot.each do |t, v| print "#{t} #{v[ev]}\n" end print "\n\n" end end if GNUPLOT_OUTPUT == true then Gnuplot.open do |gp| Gnuplot::Plot.new(gp) do |plot| plot.terminal GNPULOT_FORMAT plot.output GNUPLOT_OUTFILE plot.title "Memory usage" x = mem_snapshot.map {|n| n[0]} WATCH_TYPE.each do |ev| y = mem_snapshot.map {|n| n[1][ev]} plot.data << Gnuplot::DataSet.new([x, y]) do |ds| ds.with = "line" ds.title = ev end end end end end } end $0 = ARGV[0] fn = ARGV[0] ARGV.shift load fn, true