mruby-mmmの紹介

追記

Matzさんのアドバイス(https://twitter.com/yukihiro_matz/status/470578371789746176)でinclude MMMI, extend MMMCという煩雑なインタフェースはinclude MMMと簡単になりました。ありがとうございます。

本文

mrubyを使っていて、明らかに使わないと分かっているオブジェクトなのにGCで回収されるまで再利用出来ないなんてもったいないなと思うことが頻繁にあるかと思います。使わないと分かっているオブジェクトを再利用出来たら、そんな夢をかなえるのが名前にやたらmが多いmruby-mmm(https://github.com/miura1729/mruby-mmm)です。mmmはManual Memory Management の略です。

こんな感じで使います。

class Foo
  include MMM

  def initialize
    @a = 1
  end
end

a = Foo.new
fst = a.inspect
a.move
a = Foo.new
amov = a.inspect
a = Foo.new
anew = a.inspect

再利用したいオブジェクトのクラスは、

include MMM 

というおまじないをつけます。

というおまじないをつけます。2行にわたるのは、newをフックするのとmoveというメソッドを追加するためですが煩雑なので1行だけですむ方法があればいいなと思います。

オブジェクトの生成は普通にnewで出来ます。この先もう使わないよという場合はmoveメソッドを呼び出します。そうすると、こっそりクラス変数にmoveしたオブジェクトがキャッシュされ、つぎにnewしたときにヒープではなくキャッシュされたオブジェクトが再利用されます。moveってどっから来たの?と思われると思いますが、C++のstd::move(http://d.hatena.ne.jp/gintenlabo/20110116/1295195945)からきています。
moveの処理は、こんな感じです。

static mrb_value
mrb_mmm_move(mrb_state *mrb, mrb_value self)
{
  struct RObject *obj = mrb_obj_ptr(self);
  struct RObject *cls = (struct RObject *)obj->c;

  mrb_obj_iv_set(mrb, cls, mrb_intern_lit(mrb, "__objcache__"), self);

  return self;
}

このmruby-mmmはmrubyのJITも標準でサポートしています。オブジェクトをいっぱい生成するao-benchをmruby-mmm対応してどれくらい速くなるか調べてみました。
ao-benchに適当にmoveで再利用してもいいよということを指定します。改造したao-benchはここにあります。 https://github.com/miura1729/mruby-mmm/blob/master/sample/ao-render.rb

実行時間はこんな感じになります。i5 2.7GHz Windows7+Cygwin でmrubyのJITです。

ノーマル

$ time bin/mruby.exe benchmark/ao-render.rb
P6
64 64
255

real    0m3.050s
user    0m2.979s
sys     0m0.030s

mruby-mmm対応

$ time bin/mruby.exe mrbgems/mruby-mmm/sample/ao-render.rb
P6
64 64
255

real    0m2.490s
user    0m2.464s
sys     0m0.015s

こんな感じで効果はあります。使うのは面倒だけど。将来は、エスケープ解析やデータフロー解析してmoveを自動挿入出来るといいなと思います。