mrubyのattr系メソッドの速度(2)

「attr系メソッド使うとすごく遅いからgetter/setterは自分で書いたほうがいい」とかいう話になると、Ruby的にはあまり喜ばしくない。
mrubyはターゲットが組み込みだったり、ゲーム用途も視野に入っているということなので、そういうことを気にする人というのが現れてもおかしくない。とりあえず俺は気にする。
ラクだけど遅い、面倒だけど速い、というのはバランスが取れていて良いが、できるならRuby1.9のようにラクだし速いとなってくれたほうがいい。そこまで贅沢言わなくても、自分で定義するのと同じ速度が出るようになってくれれば好きなほうを使えばいいことになる。
同じ速度を出すためには、ようするに同じ動きになればいいわけなので、動的にVM命令列を生成してメソッドを定義するようにしてみた。ついでに複数の引数にも対応した。

#include "opcode.h"

として、

static mrb_value
mrb_mod_attr_reader(mrb_state *mrb, mrb_value mod)
{
  int i;
  int idx = mrb->irep_len;
  int ai;
  mrb_irep *irep;
  mrb_value *obj;
  int len;
  int sym_len;
  const char *name;

  mrb_get_args(mrb, "*", &obj, &len);

  for (i = 0; i < len; i++) {
    if (mrb_type(obj[i]) != MRB_TT_SYMBOL) {
      mrb_value str = mrb_funcall(mrb, obj[i], "inspect", 0);
      mrb_raise(mrb, E_TYPE_ERROR, "%s is not a symbol", mrb_string_value_ptr(mrb, str));
    }

    mrb_add_irep(mrb, idx+1);

    name = mrb_sym2name_len(mrb, mrb_symbol(obj[i]), &sym_len);
    {
      char buf[sym_len + 2];

      buf[0] = '@';
      memcpy(buf + 1, name, sym_len + 1);

      ai = mrb->arena_idx;
      irep = mrb->irep[idx] = mrb_malloc(mrb, sizeof(mrb_irep));
      irep->idx = idx++;
      irep->flags = 0;
      irep->nlocals = 3;
      irep->nregs = 4;
      irep->ilen = 3;
      irep->iseq = mrb_malloc(mrb, sizeof(mrb_code) * 3);
      irep->iseq[0] = MKOP_Ax(OP_ENTER, 0);
      irep->iseq[1] = MKOP_ABx(OP_GETIV, 0, 0);
      irep->iseq[2] = MKOP_AB(OP_RETURN, 0, OP_R_NORMAL);
      irep->slen = 1;
      irep->syms = mrb_malloc(mrb, sizeof(mrb_sym));
      irep->syms[0] = mrb_intern(mrb, buf);
      irep->plen = 0;
      irep->pool = NULL;
      mrb->irep_len = idx;
      mrb_define_method_raw(mrb, mrb_class_ptr(mod), mrb_symbol(obj[i]), mrb_proc_new(mrb, irep));
      mrb->arena_idx = ai;
    }
  }
  return mrb_nil_value();
}

あと、前回は省略したけどmrb_init_classにこれを追加。

  mrb_define_method(mrb, mod, "attr_reader", mrb_mod_attr_reader, ARGS_ANY());

mrblibのclass.rbからattr_readerを削除。

class Module
  # 15.2.2.4.13
#  def attr_reader(*names)
#    names.each{|name|
#      name2 = ('@'+name.to_s).intern
#      define_method(name){self.instance_variable_get(name2)}
#    }
#  end
  # 15.2.2.4.14
  def attr_writer(*names)

readerしか作ってないのでreaderだけのテスト。

class Foo
  def hoge
    @hoge
  end
  def hoge=(v)
    @hoge = v
  end
end

class Bar
  attr_accessor :fuga
end

from = Time.new
a = Foo.new
1000000.times do
  a.hoge# = a.hoge
end
p Time.new - from
from = Time.new
b = Bar.new
1000000.times do
  b.fuga# = b.fuga
end
p Time.new - from

#=>0.359375
#  0.359375

うん、同じだ。当たり前だが。
関係ないけどバグってるときにmake testして通ってたからattr系メソッドのテストが無いかも。

追記:@wannabe53さんも作ってた。