RObject

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)

インスタンス変数番号テーブル

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配列の初期化

        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フラグが立っていない*1のでROBJECT_LENマクロはas.heap.lenを選択しますがこちらも0です。というわけでifの中に入ります。ROBJECT_PTRも同様にas.heap.ptrを返すのですが1個目のインスタンス変数はまだ埋め込みVALUE配列に収まるので調整が行われています。

ヒープVALUE配列の確保

埋め込み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配列のサイズとして利用されています。例えば、以下のようなケース。

ちなみに、klass->iv_index_tbl->num_entriesが8だとifの中は実行されません。必要にならない限りメモリを割り当てないという方針のようです*2

                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;
            }
        }

変数値の設定

というわけで値の設定です。

        ROBJECT_PTR(obj)[index] = val;

まとめ

Ruby1.8とRuby1.9でのインスタンス変数の扱いをまとめると以下のようになります。

スピードはそんなに変わらない気がします。メモリ効率はRuby1.9の方がかなりいいと思います。不真面目なのでベンチマークとか取ったりはしませんが。


*1 rb_newobj関数にてMEMZEROされるので
*2 indexが2以下の場合もこっちにこないで埋め込みVALUE配列の方に設定されます

トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS