Boehm GC ライブラリを使って C/C++ でもガベージコレクションしよう

作成日:2002.12.19


基本的な使い方

Boehm GC ライブラリがインストールできたら、 マルチスレッドではないプログラムを Boehm GC 化してみましょう。
サンプルのプログラムは木構造を 作成するだけのプログラムですが、 これを GC 化します。
gc.h ファイルをインクルードします。 その後、 プログラム中の以下のメモリ関数を Boehm GC ライブラリのものに 置き換えて下さい。
元の関数 置き換える関数
malloc GC_malloc
calloc GC_calloc
realloc GC_realloc
free 消去
修正を加えたプログラムが tree2.c です。 -lgcオプションを加えてコンパイルしましょう。
gcc -o tree2 tree2.c -lgc
元のプログラムに対して、 修正を行うのが嫌なら 以下のようなヘッダーファイルを用意するのが よいでしょう。
#include <gc.h>
#define malloc  GC_malloc
#define calloc  GC_calloc
#define realloc GC_realloc
#define free
これで元のプログラムは Boehm GC を 使うように変更できました。
これでおしまいです。

その他の基本機能

Boehm GC の基本機能として、 malloc 系の関数の置き換え関数以外に、 以下のような関数が存在する。
void GC_gcollect(void)
明示的に GC を発生させる関数。
プログラムのフェーズの変わり目などに この関数を呼ぶことで、 Boehm GC が管理している領域のクリーンナップが 可能になる。

size_t GC_size(GC_PTR object_addr)
GC_malloc などの関数で確保したメモリ領域の サイズを返す関数。
Boehm GC はメモリ割り付け時に、 領域のサイズの大きさをメモリ領域にかきこんでおくので、 それを読み取ってくる関数である。
当然、 obj_addr は Boehm GC によって割り付けられた メモリ領域の先頭アドレスである必要がある。
char* p = (char*)GC_malloc(1000);
size = GC_size(p);  /* GC_size は 1000 を返す。*/
GC_PTR GC_base(GC_PTR displaced_pointer)
この関数は、 構造体や配列のような大きなサイズのオブジェクトがあり、 displaced_pointerが その内部のアドレスを指している時、 オブジェクトの先頭アドレスを返してくれる関数である。
char *p, *q, *r 
p = (char*)GC_malloc(1000);
q = p + index ;
r = GC_base(q);  /* r は p と等しくなる。*/
displaced_pointerは Boehm GC によって割り付けられ、 まだ回収されていないオブジェクトの内部を指す必要がある。
この関数は、 常に先頭アドレスを見つけられるとは限らない。 分からない場合には、0 を返してくる。 また、Boehm GC によって管理されていない領域のポインタを、 displaced_pointerにセットして渡し場合、 エラーが発生する危険性がある。

GC_PTR GC_malloc_uncollectable(size_t size_in_bytes)
この関数は Boehm GC の管理している領域に、 GC によって回収されないメモリ領域を陽にとることが できる。
この関数で割り当てたオブジェクトは GC_free で回収する 必要がある。

void GC_free(GC_PTR object_addr)
Boehm GC が割り当てたオブジェクトを 明示的に解放する関数。 主として GC_malloc_uncollectable で割り当てた オブジェクトを回収する。
object_addrで指定できるのは、 Boehm GC によって割り付けられたオブジェクトのみで、 でたらめなアドレスや malloc/calloc によって取ってきた オブジェクトを指定されると、 エラーとなる可能性がある。
GC_free(0)の場合、 何も操作をしない(安全性は保証されている)。

GC の動作状況を知る

サンプルプログラムは何の出力も行いませんでしたから、 本当に GC が行われたのかどうか釈然としません。
GC が行われたことをきちんと確かめましょう。

gc.hに定義されているGC_gc_noは、 Boehm GC ライブラリが起動してから何回 GC を 起こしたのかをカウントする変数です。 GC_gc_nogc.h の中で 以下のように定義されています。 GC_wordunsigned longの 別名となっています。
//typedef unsigned long GC_word;
GC_word  GC_gc_no;
サンプルプログラムのメインループの繰り返しに、 GC_gc_no を表示するコードを 挿入してみました。
for(i = 0; i<100 ; i++){
  Tree* root = generate_tree(100);
  printf("GC counts: %d\n", GC_gc_no );
}
GC 回数以外に、 Boehm GC が管理しているメモリのパラメータを知ることが可能です。
関数 解説
size_t GC_get_heap_size(void) (Boehm GC が管理する)ヒープ領域の大きさサイズ
size_t GC_get_free_bytes(void) (同)ヒープ領域中の空き領域
size_t GC_get_bytes_since_gc(void) 最後の GC が起きてから割り付けたオブジェクトのサイズ
size_t GC_get_total_bytes(void) プロセスが起動してから割り付けたオブジェクトの総サイズ
tree3.cは、 サンプルプログラムを GC の状況を報告するように 修正したものです。



この方法は、プログラム内部から Boehm GC の状況を 知ることができるのですが、 Java の -verbose:gc 表示のように、 GC が起こる度に状況を報告して欲しい場合があります。
Boehm GC では、 ライブラリコンパイル時にこの機能がオフになっています。 Makefile の中のコンパイラにあたえるマクロ定義 DEFS の中にある -DSILENT を削除してビルドすることによって、 この機能に対応します。
# DEFS = ...  -DSILENT=1 ...
DEFS = ...   ...
SILENTマクロを外してビルドしたライブラリを使うと、 GC が起こる度に大量の内部状況を出力するようになります。
--> Marking for collection 11 after 9007104 allocd bytes + 49152 wasted bytes
Collection 10 reclaimed 8192 bytes ---> heapsize = 20586496 bytes
World-stopped marking took 210 msecs
Bytes recovered before sweep - f.l. count = 0
Immediately reclaimed 8192 bytes in heap of size 20586496 bytes
16384 (atomic) + 20561920 (composite) collectable bytes in use
Finalize + initiate sweep took 0 + 0 msecs
Complete collection took 220 msecs
Increasing heap size by 6864896 after 4096 allocated bytes
Increasing heap size by 8388608 after 6864896 allocated bytes
Initiating full world-stop collection 12 after 15253504 allocd bytes
4096 bytes in heap blacklisted for interior pointers
この出力情報は詳細で、GC に掛かった時間やメモリサイズ等の情報を得ること ができます。
ただし、この出力がいつも出ていると鬱陶しいので プログラムの適当な箇所で GC_quietに 0 以外の値を代入すること停止させましょう。 停止した GC 出力は、GC_quiet を 0 にすることで再開することもできます。

戻る
Written by NAKAMURA Minoru, Email: nminoru atmark nminoru dot jp, Twitter:@nminoru_jp