#contents *RObject [#u173efc8] Ruby1.9ではObjectのC表現RObject構造体がかなり変わっています。参考までにRuby1.8のRObject構造体はこうです。 struct RObject { struct RBasic basic; struct st_table *iv_tbl; }; シンプルです。次にRuby1.9のRObject構造体です。((unionの名前がasなのは"as array"等と読めるようにとの配慮らしいです by Yuguiさん)) #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) [#q1dde9b3] ***インスタンス変数番号テーブル [#tfafe155] 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の定義は以下の通り。((superとかがrb_classext_tに外出しされている理由はRClassのサイズをあまり大きくできないかららしいです。詳しくはRHGの「2.3.3 構造体の隙間」参照)) typedef struct { VALUE super; struct st_table *iv_tbl; } rb_classext_t; 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配列の初期化 [#r3b9f965] 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配列の確保 [#x9237e8d] 埋め込み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; } } ***変数値の設定 [#f0740f22] というわけで値の設定です。 ROBJECT_PTR(obj)[index] = val; ***ところで何故埋め込めるのか [#jaf6eaa2] 何で埋め込んでいるのか、は何となくわかると思います。では何故埋め込めるのでしょうか?別の言い方をすると何故埋め込む空間なんてあるのでしょうか?そこら辺はRHGの「2.3.3 構造体の隙間」を見るとわかります。要約すると「オブジェクト構造体用のメモリは全部のオブジェクト構造体をunionしたRVALUE単位で割り当てるので、あるオブジェクト構造体だけでかいと無駄が発生する」ということになります。この無駄な空間をどうせだから埋め込み用に使おうという発想なようです。 **まとめ [#g43c94b7] Ruby1.8とRuby1.9でのインスタンス変数の扱いをまとめると以下のようになります。 -Ruby1.8 --変数の取得方法:id→VALUEのテーブルから取得 --各RObjectが変数テーブルを管理 --変数追加の際、常にメモリ確保 -Ruby1.9 --変数の取得方法:id→indexのテーブルの後、VALUE配列から取得 --RClassが変数番号テーブルを管理 --変数追加の際、必要があったらメモリ確保 スピードはそんなに変わらない気がします。メモリ効率はRuby1.9の方がかなりいいと思います。不真面目なのでベンチマークとか取ったりはしませんが。((ループを回してそのたびにインスタンス変数を定義すると最悪らしいです:-P)) *謝辞 [#e80bcc09] 「ところで何故埋め込めるのか」の部分は[[第1回 RHGの逆襲>http://qwik.jp/rhg-strikes-back/37.html]]に参加することで知りました。このような会を開いてくださったYuguiさん、会場を用意してくださったよしおかさん、ささださん、その他多くの方に感謝します。