Rubyのアリーナの覗いてみる

最近、変なRubyの重箱の隅をつつくようなプログラムばっかり作っていますが、その1つです。現状のRubyの実現ではObject#__id__で返される値はオブジェクトのアドレスから作るため、Object#__id__の結果をビットシフトすることでRubyレベルでオブジェクトのアドレスが得られます。そして、ObjectSpace#each_objectを使うと全部のオブジェクトが得られます。
これらを利用して、メモリの使用状況を絵にするプログラムを作ってみました。256×256のビットマップをメモリに見立てています。Rubyで使っているところ(正確にはObject#each_objectで手に入れられるオブジェクト)は赤く表示されます。

$no = 0
def make_bmp(memtab)
  fheader = ['BM', 65536 + 12 + 14, 0, 0, 12 + 14 + 256].pack("a2VvvV")
  iheader = [12, 256, 256, 1, 8].pack("Vvvvv")
  black = [255, 255, 255].pack("c3")
  white = [0, 0, 255].pack("c3")
  File.open("mem-#{$no}.bmp", "w") do |fp|
    fp.print fheader
    fp.print iheader
    fp.print black
    fp.print white*255
    65535.downto(0) do |i|
      if memtab[i] then
        fp.printf "%c", 1
      else
        fp.printf "%c", 0
      end
    end
  end
  $no = $no + 1
end

def dump_mem
  max = 0
  min = 65536 * 65536
  objs = {}
  ObjectSpace.each_object {|obj|
    add = ((obj.__id__) >> 1) << 2
    if max < add then
      max = add
    end

    if add < min then
      min = add
    end
  }

  range = max - min
  if range > 65536 then
    step = range / 65536 + 1
  else
    step = 1
  end
  
  printf "Address max %x\n", max
  printf "Address min %x\n", min
  printf "step %d\n", step

  memtab = {}
  ObjectSpace.each_object {|obj|
    add = ((obj.__id__) >> 1) << 2
    memtab[(add - min) / step] = 1
  }
  make_bmp(memtab)
end

サンプルです。

dump_mem   # mem-0.bmp

str = ""
(0..1000).each do |n|
  str = str + "a"
end

dump_mem    # mem-1.bmp

ObjectSpace.garbage_collect

dump_mem    # mem-2.bmp

dump_memを実行すると、mem-?.bmpというファイルがカレントディレクトリに作られます。?は0から呼ばれた順番に1づつ増えます。

サンプルの実行例です。bmpがアップロードできなかったのでpngに変換しています。

mem-0.bmp

mem-1.bmp

mem-2.bmp