From: SASADA Koichi Date: 2010-11-03T17:59:45+09:00 Subject: [ruby-dev:42520] GC issues  ささだです。  GC まわりを見てたんですが、いくつか疑問点がありました。 1. ファイナライザの起動タイミング  現在、ファイナライザは after_gc_sweep() のタイミングでしか起動されませ ん。つまり、すべての sweep が完了するまで遅延するわけですが、これは資源 解放のタイミングを遅くすることになるので、まずいような気がします。  これを、slot_sweep のたびにファイナライザを呼ぶようにしたら、メモリが 少ない環境でも、test-all が最後まで動くようになりました。 2. GC.stress が true 時の挙動  GC.stress が true の時は、mark/free を行い、オブジェクト空間が(それな りに)綺麗になっている、ということを期待しているように思いますが、現在は gc_lazy_sweep() を行うだけなので、まだ sweep が完了していない場合は、 mark などは起こりません。  とりあえず 1 は問題だろうと思います。  あと、諸々見ていった結果のパッチ。 Index: gc.c =================================================================== --- gc.c (revision 29675) +++ gc.c (working copy) @@ -1043,12 +1043,25 @@ rb_during_gc(void) #define RANY(o) ((RVALUE*)(o)) -static VALUE -rb_newobj_from_heap(rb_objspace_t *objspace) +VALUE +rb_newobj(void) { + rb_objspace_t *objspace = &rb_objspace; VALUE obj; - if ((ruby_gc_stress && !ruby_disable_gc_stress) || !freelist) { + if (during_gc) { + dont_gc = 1; + during_gc = 0; + rb_bug("object allocation during garbage collection phase"); + } + + if (ruby_gc_stress && !ruby_disable_gc_stress) { + if (!garbage_collect(objspace)) { + during_gc = 0; + rb_memerror(); + } + } + if (!freelist) { if (!gc_lazy_sweep(objspace)) { during_gc = 0; rb_memerror(); @@ -1068,20 +1081,6 @@ rb_newobj_from_heap(rb_objspace_t *objsp return obj; } -VALUE -rb_newobj(void) -{ - rb_objspace_t *objspace = &rb_objspace; - - if (during_gc) { - dont_gc = 1; - during_gc = 0; - rb_bug("object allocation during garbage collection phase"); - } - - return rb_newobj_from_heap(objspace); -} - NODE* rb_node_newnode(enum node_type type, VALUE a0, VALUE a1, VALUE a2) { @@ -1947,6 +1946,11 @@ slot_sweep(rb_objspace_t *objspace, stru objspace->heap.free_num += free_num; } objspace->heap.final_num += final_num; + + if (final_num > 0) { + /* kick finalizer */ + RUBY_VM_SET_FINALIZER_INTERRUPT(GET_THREAD()); + } } static int @@ -1976,6 +1980,11 @@ before_gc_sweep(rb_objspace_t *objspace) } objspace->heap.sweep_slots = heaps; objspace->heap.free_num = 0; + + /* sweep unlinked method entries */ + if (GET_VM()->unlinked_method_entry_list) { + rb_sweep_method_entry(GET_VM()); + } } static void @@ -1995,20 +2004,9 @@ after_gc_sweep(rb_objspace_t *objspace) } malloc_increase = 0; - if (deferred_final_list) { - /* clear finalization list */ - RUBY_VM_SET_FINALIZER_INTERRUPT(GET_THREAD()); - } - else{ free_unused_heaps(objspace); } - /* sweep unlinked method entries */ - if (th->vm->unlinked_method_entry_list) { - rb_sweep_method_entry(th->vm); - } -} - static int lazy_sweep(rb_objspace_t *objspace) { @@ -2785,17 +2783,20 @@ run_single_final(VALUE arg) } static void -run_finalizer(rb_objspace_t *objspace, VALUE obj, VALUE objid, VALUE table) +run_finalizer(rb_objspace_t *objspace, VALUE objid, VALUE table) { long i; int status; VALUE args[3]; - args[1] = 0; - args[2] = (VALUE)rb_safe_level(); - if (!args[1] && RARRAY_LEN(table) > 0) { + if (RARRAY_LEN(table) > 0) { args[1] = rb_obj_freeze(rb_ary_new3(1, objid)); } + else { + args[1] = 0; + } + + args[2] = (VALUE)rb_safe_level(); for (i=0; iobj, rb_obj_id(curr->obj), curr->table); + run_finalizer(objspace, rb_obj_id(curr->obj), curr->table); st_delete(finalizer_table, (st_data_t*)&curr->obj, 0); list = curr->next; xfree(curr); @@ -2973,7 +2967,8 @@ rb_gc(void) { rb_objspace_t *objspace = &rb_objspace; garbage_collect(objspace); - gc_finalize_deferred(objspace); + finalize_deferred(objspace); + free_unused_heaps(objspace); } /*  見ながらメモっていったもの。似たような関数があって、ちょっとわからなく なったので整理。 slot_sweep(rb_objspace_t *objspace, struct heaps_slot *sweep_slot) あるスロットについて、sweep を行う。sweep_slot にあったものを取ってくる。 ready_to_gc(rb_objspace_t *objspace) GC をやって良ければ(dont_gc || during_gc)TRUE を返す もし、やっちゃ駄目で freelist が 0 なら、heaps を拡張して対応。 before_gc_sweep(rb_objspace_t *objspace) mark 終了後、sweep を開始する前処理。 after_gc_sweep(rb_objspace_t *objspace) sweep 終了後の後処理。 lazy_sweep(rb_objspace_t *objspace) freelist に何かしらの値が入るまで、lazy sweep を進める。 進めても、freelist に値が入っていなければ、FALSE を返す。 rest_sweep(rb_objspace_t *objspace) まだ sweep していない slot が残っていれば、それをすべて sweep する。 gc_lazy_sweep(rb_objspace_t *objspace) Lazy sweep 版 garbage_collect newobj は、この関数を呼ぶ - もし、sweep の途中であれば、lazy_sweep() する freelist が埋まれば、そこで返す - mark をする - 最初の1個のオブジェクトが見つかるまで、sweep をする みつかった時点で、sweep 中断 gc_sweep(rb_objspace_t *objspace) 全部 sweep する ------------------------------------------ finalize_list(rb_objspace_t *objspace, RVALUE *p) p は RVALUE の連結リスト。このリストはファイナライザ対象。 run_final(rb_objspace_t *objspace, VALUE obj) obj の後始末を行う - T_DATA の場合は登録された free 関数を呼ぶ - Ruy レベルでファイナライザが登録されていれば、それを呼ぶ(run_finalizer) run_finalizer(rb_objspace_t *objspace, VALUE obj, VALUE objid, VALUE table) Ruby レベルで登録されたファイナライザを起動する - ファイナライザは複数登録されているかも。それが talbe という配列。 - これ、obj 要らないじゃん。 - table って関数名は変な感じ。 run_single_final(VALUE arg) ファイナライザを一つ起動する。run_finalizerから、rb_protec で呼ばれる。 finalize_deferred(rb_objspace_t *objspace) 遅延されたファイナライザを起動する。 - deferred_final_list で示される連結リスト - 起動する前に、deferred_final_list をクリア gc_finalize_deferred(rb_objspace_t *objspace) ファイナライザを呼んで、その後使われていないヒープを回収。 - finalize_deferred(objspace); - free_unused_heaps(objspace); 同じようなこと関数が多いので、これは廃止したほうが良さそう。 rb_gc_finalize_deferred(void) gc_finalize_deferred を、gc.c から呼ぶためのインターフェース。 thread.c で、割り込み処理のところから使われている。 - free_unused_heaps は呼ばなくていいんではないか? つまり、after_gc_sweep だけで呼べばいいと思われる。 -- // SASADA Koichi at atdot dot net