Created
February 1, 2014 15:20
-
-
Save pochi/8753646 to your computer and use it in GitHub Desktop.
Ruby 2.1のGCについて、RubyHackingGuideを見ながら理解してみようとしたログ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## Rubyのオブジェクトについて | |
### オブジェクトの定義 | |
include/ruby/ruby.hにある。 | |
それぞれのクラスの定義を抜粋する。 | |
```ruby | |
struct RBasic { | |
VALUE flags; | |
const VALUE klass; | |
} | |
struct RObject { | |
struct RBasic basic; | |
union { | |
struct { | |
long numiv; | |
VALUE *ivptr; | |
struct st_table *iv_index_tbl; /* shortcut for RCLASS_IV_INDEX_TBL(rb_obj_class(obj)) */ | |
} heap; | |
VALUE ary[ROBJECT_EMBED_LEN_MAX]; | |
} as; | |
}; | |
# クラスオブジェクト | |
struct RClass { | |
struct RBasic basic; | |
VALUE super; | |
rb_classext_t *ptr; | |
struct method_table_wrapper *m_tbl_wrapper; | |
}; | |
# Floatオブジェクト | |
struct RFloat { | |
struct RBasic basic; | |
double float_value; | |
}; | |
# Stringオブジェクト | |
struct RString { | |
struct RBasic basic; | |
union { | |
struct { | |
long len; | |
char *ptr; | |
union { | |
long capa; | |
VALUE shared; | |
} aux; | |
} heap; | |
char ary[RSTRING_EMBED_LEN_MAX + 1]; | |
} as; | |
}; | |
``` | |
## RubyのGCについて | |
Rubyソースコード完全ガイドの第5章と一緒に見ていく。 | |
ただ、Ruby2.1は世代別GCになったから難しいかも。 | |
### C言語のメモリ管理について | |
以下の4層にあるイメージ。 | |
- テキスト領域 | |
- スタティック変数やグローバル変数の置き場 | |
- マシンスタック | |
- Heap | |
Heapはmallocで受け取るところ。 | |
テキスト領域はソースコード。 | |
マシンスタックは関数の置き場。 | |
関数一つにスタックフレームがよばれ、returnするとマシンスタックからおりる。 | |
### alloca | |
マシンスタック版malloc | |
missing/alloca.cにallocaをサポートしていない環境のために実装がある。 | |
### GCとは何か | |
- 細切れになっているメモリを整理する | |
- メモリの回収を行う | |
mallocとfreeはいつもペアだ! | |
Rubyはコンパクションしないらしいぜ・・・ | |
### GCとは何をするということか | |
参照されなくなったオブジェクトを回収する。 | |
確実に必要なオブジェクトをGCのルートとよぶ。 | |
Rubyは基本マークあんどスイープ。 | |
GCのことをそこまで考えることないのが楽。 | |
でもスイープするためにオブジェクト全部なめないといけない。 | |
### ストップ&コピー | |
これはGC対象領域を2つ作成する方法。 | |
まずは一つの領域に対してすべてのオブジェクトを作成する。 | |
GCのフェーズになると確実に参照されるオブジェクトをもう一つの領域に移す。 | |
そうなると残った領域はそのまま廃棄できて効率が良く、 | |
同時にコンパクションもできる。 | |
でもオブジェクト参照位置はかわってしまうし、領域が2倍必要という | |
デメリットもある。 | |
### リファレンスカウント | |
オブジェクトが参照している数をカウントする。 | |
0になったということは解放できるオブジェクトだということ。 | |
ただし、カウント操作が難しいこととお互いのオブジェクトをお互いが | |
さすような状況になると一生解放されないことになる。 | |
Python2.2では基本リファレンスカウントを利用している。 | |
循環しないようにするためにたまにマーク&スイープして対応している。 | |
### オブジェクトの管理 | |
Rubyが自分で作ったオブジェクトじゃないと管理してくれない。 | |
例えばRubyの中で自分でCでmallocしたようなものは即座にメモリリーク対象となる。 | |
Rubyではnilとかtrueとか特殊なオブジェクトをのぞいて基本的には | |
RVALUEとよばれる構造体を利用してGCを行う。 | |
```ruby | |
typedef struct RVALUE { | |
union { | |
struct { | |
VALUE flags; /* always 0 for freed obj */ | |
struct RVALUE *next; | |
} free; | |
struct RBasic basic; | |
struct RObject object; | |
struct RClass klass; | |
struct RFloat flonum; | |
struct RString string; | |
struct RArray array; | |
struct RRegexp regexp; | |
struct RHash hash; | |
struct RData data; | |
struct RTypedData typeddata; | |
struct RStruct rstruct; | |
struct RBignum bignum; | |
struct RFile file; | |
struct RNode node; | |
struct RMatch match; | |
struct RRational rational; | |
struct RComplex complex; | |
struct { | |
struct RBasic basic; | |
VALUE v1; | |
VALUE v2; | |
VALUE v3; | |
} values; | |
} as; | |
#if GC_DEBUG | |
const char *file; | |
VALUE line; | |
#endif | |
} RVALUE; | |
``` | |
ポイントはここのflagsでこれが0だとGC対象になる。 | |
Rubyのオブジェクトは全てRBasicという要素をもつので、 | |
as.basic.flagsを見て0かどうかでそのオブジェクトの生死を確認できる。 | |
### オブジェクトヒープ | |
RubyHackingGuideから大幅に変更されている。 | |
関係されていそうな構造体を順番にみていって整理する。 | |
まずはrb_heap_struct | |
``` | |
typedef struct rb_heap_struct { | |
struct heap_page *pages; | |
struct heap_page *free_pages; | |
struct heap_page *using_page; | |
struct heap_page *sweep_pages; | |
RVALUE *freelist; | |
size_t page_length; /* total page count in a heap */ | |
size_t total_slots; /* total slot count (page_length * HEAP_OBJ_LIMIT) */ | |
} rb_heap_t; | |
``` | |
以下のページをそれぞれもっているみたい | |
- 利用可能ページ | |
- 利用しているページ | |
- スイープしたページ | |
やっぱり、こうやってみるとまだマーク&スイープなのかなー。 | |
お次にrb_objspace | |
``` | |
typedef struct rb_objspace { | |
struct { | |
size_t limit; | |
size_t increase; | |
#if MALLOC_ALLOCATED_SIZE | |
size_t allocated_size; | |
size_t allocations; | |
#endif | |
} malloc_params; | |
rb_heap_t eden_heap; | |
rb_heap_t tomb_heap; /* heap for zombies and ghosts */ | |
struct { | |
struct heap_page **sorted; | |
size_t used; | |
size_t length; | |
RVALUE *range[2]; | |
size_t limit; | |
size_t increment; | |
size_t swept_slots; | |
size_t min_free_slots; | |
size_t max_free_slots; | |
/* final */ | |
size_t final_slots; | |
RVALUE *deferred_final; | |
} heap_pages; | |
struct { | |
int dont_gc; | |
int dont_lazy_sweep; | |
int during_gc; | |
rb_atomic_t finalizing; | |
} flags; | |
st_table *finalizer_table; | |
mark_stack_t mark_stack; | |
struct { | |
int run; | |
gc_profile_record *records; | |
gc_profile_record *current_record; | |
size_t next_index; | |
size_t size; | |
#if GC_PROFILE_MORE_DETAIL | |
double prepare_time; | |
#endif | |
double invoke_time; | |
#if USE_RGENGC | |
size_t minor_gc_count; | |
size_t major_gc_count; | |
#if RGENGC_PROFILE > 0 | |
size_t generated_normal_object_count; | |
size_t generated_shady_object_count; | |
size_t shade_operation_count; | |
size_t promote_infant_count; | |
#if RGENGC_THREEGEN | |
size_t promote_young_count; | |
#endif | |
size_t remembered_normal_object_count; | |
size_t remembered_shady_object_count; | |
#if RGENGC_PROFILE >= 2 | |
size_t generated_normal_object_count_types[RUBY_T_MASK]; | |
size_t generated_shady_object_count_types[RUBY_T_MASK]; | |
size_t shade_operation_count_types[RUBY_T_MASK]; | |
size_t promote_infant_types[RUBY_T_MASK]; | |
#if RGENGC_THREEGEN | |
size_t promote_young_types[RUBY_T_MASK]; | |
#endif | |
size_t remembered_normal_object_count_types[RUBY_T_MASK]; | |
size_t remembered_shady_object_count_types[RUBY_T_MASK]; | |
#endif | |
#endif /* RGENGC_PROFILE */ | |
#endif /* USE_RGENGC */ | |
/* temporary profiling space */ | |
double gc_sweep_start_time; | |
size_t total_allocated_object_num_at_gc_start; | |
size_t heap_used_at_gc_start; | |
/* basic statistics */ | |
size_t count; | |
size_t total_allocated_object_num; | |
size_t total_freed_object_num; | |
int latest_gc_info; | |
} profile; | |
struct gc_list *global_list; | |
rb_event_flag_t hook_events; /* this place may be affinity with memory cache */ | |
VALUE gc_stress; | |
struct mark_func_data_struct { | |
void *data; | |
void (*mark_func)(VALUE v, void *data); | |
} *mark_func_data; | |
#if USE_RGENGC | |
struct { | |
int during_minor_gc; | |
int parent_object_is_old; | |
int need_major_gc; | |
size_t remembered_shady_object_count; | |
size_t remembered_shady_object_limit; | |
size_t old_object_count; | |
size_t old_object_limit; | |
#if RGENGC_THREEGEN | |
size_t young_object_count; | |
#endif | |
#if RGENGC_ESTIMATE_OLDMALLOC | |
size_t oldmalloc_increase; | |
size_t oldmalloc_increase_limit; | |
#endif | |
#if RGENGC_CHECK_MODE >= 2 | |
struct st_table *allrefs_table; | |
size_t error_count; | |
#endif | |
} rgengc; | |
#endif /* USE_RGENGC */ | |
} rb_objspace_t; | |
``` | |
ながい、ながいぞー。 | |
でも結構重要なこと書いてありそうだから順番に見ていく。 | |
まずはmalloc_paramsとよばれる構造体。 | |
``` | |
struct { | |
size_t limit; | |
size_t increase; | |
#if MALLOC_ALLOCATED_SIZE | |
size_t allocated_size; | |
size_t allocations; | |
#endif | |
} malloc_params; | |
``` | |
malloc関連の設定かなー。 | |
### メモリレイヤーについて | |
上から以下の通り | |
* テキスト領域 | |
* スタティック変数やグローバル変数置き場 | |
* マシンスタック | |
* ヒープ | |
### 用語解説 | |
* malloc | |
* ヒープ上にメモリを獲得できる | |
* alloca | |
* マシンストック上にメモリを確保する。マシンストックは関数でreturnするとメモリも解放される | |
### xmallocたるものがあるよ | |
Cのmallocとなにがちがうんだろう. | |
まずは以下のようなaliasがある。 | |
./missing/alloca.c | |
#define xmalloc ruby_xmalloc | |
ruby_xmallocの実装はgc.cにある。 | |
```ruby | |
void * | |
ruby_xmalloc(size_t size) | |
{ | |
return objspace_xmalloc(&rb_objspace, size); | |
} | |
``` | |
objspace_xmalloc(&rb_objspace, size)もgc.cにある。 | |
```ruby | |
static void * | |
objspace_xmalloc(rb_objspace_t *objspace, size_t size) | |
{ | |
void *mem; | |
size = objspace_malloc_prepare(objspace, size); | |
TRY_WITH_GC(mem = malloc(size)); | |
size = objspace_malloc_size(objspace, mem, size); | |
objspace_malloc_increase(objspace, mem, size, 0, MEMOP_TYPE_MALLOC); | |
return objspace_malloc_fixup(objspace, mem, size); | |
} | |
``` | |
順番に見ていくとまずはobjspace_mallock_prepare. | |
```ruby | |
static inline size_t | |
objspace_malloc_size(rb_objspace_t *objspace, void *ptr, size_t hint) | |
{ | |
#ifdef HAVE_MALLOC_USABLE_SIZE | |
return malloc_usable_size(ptr); | |
#else | |
return hint; | |
#endif | |
} | |
``` | |
メモリサイズを返すみたい。mallocのサイズに制限があれば現在のメモリ地点から | |
計算するもよう。 | |
次はTRY_WITH_GCでメモリサイズをとりにいく。 | |
```ruby | |
#define TRY_WITH_GC(alloc) do { \ | |
if (!(alloc) && \ | |
(!garbage_collect_with_gvl(objspace, 1, 1, GPR_FLAG_MALLOC) || /* full mark && immediate sweep */ \ | |
!(alloc))) { \ | |
ruby_memerror(); \ | |
} \ | |
} while (0) | |
``` | |
ここでgarbage_collect_with_gvlがよばれる。 | |
コメントにもあるとおり、第二、第三引数が1なのはfull markとimmediate sweepを行うため。 | |
```ruby | |
static int | |
garbage_collect_with_gvl(rb_objspace_t *objspace, int full_mark, int immediate_sweep, int reason) | |
{ | |
if (dont_gc) return TRUE; | |
if (ruby_thread_has_gvl_p()) { | |
return garbage_collect(objspace, full_mark, immediate_sweep, reason); | |
} | |
else { | |
if (ruby_native_thread_p()) { | |
struct objspace_and_reason oar; | |
oar.objspace = objspace; | |
oar.reason = reason; | |
oar.full_mark = full_mark; | |
oar.immediate_sweep = immediate_sweep; | |
return (int)(VALUE)rb_thread_call_with_gvl(gc_with_gvl, (void *)&oar); | |
} | |
else { | |
/* no ruby thread */ | |
fprintf(stderr, "[FATAL] failed to allocate memory\n"); | |
exit(EXIT_FAILURE); | |
} | |
} | |
} | |
``` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment