Skip to content

Instantly share code, notes, and snippets.

@pochi
Created February 1, 2014 15:20
Show Gist options
  • Save pochi/8753646 to your computer and use it in GitHub Desktop.
Save pochi/8753646 to your computer and use it in GitHub Desktop.
Ruby 2.1のGCについて、RubyHackingGuideを見ながら理解してみようとしたログ
## 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