- 追加された行はこの色です。
- 削除された行はこの色です。
#contents
*RObject [#t11da917]
*RObject [#m232ff7d]
Ruby1.9ではObjectのC表現RObject構造体がかなり変わっています。参考までにRuby1.8のRObject構造体はこうです。
struct RObject {
struct RBasic basic;
struct st_table *iv_tbl;
};
シンプルです。次にRuby1.9のRObject構造体です。
#define ROBJECT_EMBED_LEN_MAX 3
struct RObject {
struct RBasic basic;
union {
struct {
long len;
VALUE *ptr;
} heap;
VALUE ary[ROBJECT_EMBED_LEN_MAX];
} as;
};
#define ROBJECT_EMBED FL_USER1
#define ROBJECT_LEN(o) \
((RBASIC(o)->flags & ROBJECT_EMBED) ? \
ROBJECT_EMBED_LEN_MAX : \
ROBJECT(o)->as.heap.len)
#define ROBJECT_PTR(o) \
((RBASIC(o)->flags & ROBJECT_EMBED) ? \
ROBJECT(o)->as.ary : \
ROBJECT(o)->as.heap.ptr)
すっげー複雑です:-)。読解しましょう。
**rb_ivar_set(variable.c) [#u169610b]
***インスタンス変数番号テーブル [#f73535cc]
Ruby1.8のiv_tblはインスタンス変数のテーブルです。というわけでインスタンス変数を操作する関数を見てみましょう。
switch (TYPE(obj)) {
case T_OBJECT:
klass = rb_obj_class(obj);
if (!RCLASS_IV_INDEX_TBL(klass))
RCLASS_IV_INDEX_TBL(klass) = st_init_numtable();
Ruby1.9ではRObject単独でインスタンス変数を管理するのではなくRClassと連動するようです。RClassの定義は以下の通り。
struct RClass {
struct RBasic basic;
rb_classext_t *ptr;
struct st_table *m_tbl;
struct st_table *iv_index_tbl;
};
#define RCLASS_IV_INDEX_TBL(c) (RCLASS(c)->iv_index_tbl)
先に進みましょう。
ivar_extended = 0;
if (!st_lookup(RCLASS_IV_INDEX_TBL(klass), id, &index)) {
index = RCLASS_IV_INDEX_TBL(klass)->num_entries;
st_add_direct(RCLASS_IV_INDEX_TBL(klass), id, index);
ivar_extended = 1;
}
変数名を示すidからindexを拾う、ないのならエントリの追加を行っています。つまり、Rclass.iv_index_tblはこういう構造なようです。
|key(id)|value(index)|
|ID(x)|0|
|ID(y)|1|
***埋め込みVALUE配列の初期化 [#rf3748e9]
len = ROBJECT_LEN(obj);
if (len <= index) {
VALUE *ptr = ROBJECT_PTR(obj);
if (index < ROBJECT_EMBED_LEN_MAX) {
RBASIC(obj)->flags |= ROBJECT_EMBED;
ptr = ROBJECT(obj)->as.ary;
for (i = 0; i < ROBJECT_EMBED_LEN_MAX; i++) {
ptr[i] = Qundef;
}
}
初めてrb_ivar_setが呼ばれた場合、ROBJECT_EMBEDフラグが立っていない((rb_newobj関数にてMEMZEROされるので))のでROBJECT_LENマクロはas.heap.lenを選択しますがこちらも0です。というわけでifの中に入ります。ROBJECT_PTRも同様にas.heap.ptrを返すのですが1個目のインスタンス変数はまだ埋め込みVALUE配列に収まるので調整が行われています。
***ヒープVALUE配列の確保 [#g6d4c1cb]
埋め込みVALUE配列に収まらない場合、ヒープにVALUE配列が確保されています。
else {
VALUE *newptr;
long newsize = (index+1) + (index+1)/4; /* (index+1)*1.25 */
if (!ivar_extended &&
RCLASS_IV_INDEX_TBL(klass)->num_entries < newsize) {
newsize = RCLASS_IV_INDEX_TBL(klass)->num_entries;
}
番号テーブルは全インスタンスで共有されているので、新しい変数が追加されるわけではない場合は現在の番号テーブルサイズがヒープVALUE配列のサイズとして利用されています。例えば、以下のようなケース。
-そのクラスの新しいオブジェクト
-index => 4
-klass->iv_index_tbl->num_entries => 5
ちなみに、klass->iv_index_tbl->num_entriesが8だとifの中は実行されません。必要にならない限りメモリを割り当てないという方針のようです((indexが2以下の場合もこっちにこないで埋め込みVALUE配列の方に設定されます))。
if (RBASIC(obj)->flags & ROBJECT_EMBED) {
newptr = ALLOC_N(VALUE, newsize);
MEMCPY(newptr, ptr, VALUE, len);
RBASIC(obj)->flags &= ~ROBJECT_EMBED;
ROBJECT(obj)->as.heap.ptr = newptr;
}
else {
REALLOC_N(ROBJECT(obj)->as.heap.ptr, VALUE, newsize);
newptr = ROBJECT(obj)->as.heap.ptr;
}
for (; len < newsize; len++)
newptr[len] = Qundef;
ROBJECT(obj)->as.heap.len = newsize;
}
}
***変数値の設定 [#yf24a914]
というわけで値の設定です。
ROBJECT_PTR(obj)[index] = val;
**まとめ [#j48ea628]
Ruby1.8とRuby1.9でのインスタンス変数の扱いをまとめると以下のようになります。
-Ruby1.8
--変数の取得方法:id→VALUEのテーブルから取得
--各RObjectが変数テーブルを管理
--変数追加の際、常にメモリ確保
-Ruby1.9
--変数の取得方法:id→indexのテーブルの後、VALUE配列から取得
--RClassが変数番号テーブルを管理
--変数追加の際、必要があったらメモリ確保
スピードはそんなに変わらない気がします。メモリ効率はRuby1.9の方がかなりいいと思います。不真面目なのでベンチマークとか取ったりはしませんが。