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さんも作ってた。