Precise Garbage Collection for C
            の紹介


        CSNagoya 三浦英樹
Precise Garbage Collection for C
著者
 Jon Rafkind, Adam Wick, John Regehr, Matthew Flatt
入手先
http://www.cs.utah.edu/~regehr/papers/ismm15-rafkind.pdf
CのプログラムでもGC使いたい
• 普通、保守的 GCでしょう
• たとえば、Boehm GCとか
• でも、長い時間使ってるとメモリー使用量
  がすごく増大する場合があるみたいだよ
• 証拠は?
• それは、次のスライドの心だー
PLT Schemeにあった怖い話
• PLT Schemeを使用したプログラム環境
  DrSchemeは毎日再起動をしなければならなかっ
  た。でも、正確なGCに変えたら再起動が必要なく
  なった
• バイトコードとドキュメントを生成すると、正確な
  GCだと200MBで済むところが、保守的GCだと
  700MBも必要だった
• PLT Schemeで作ったmail clientを1日おきに再起
  動していたが、正確なGCに変えたら再起動しなく
  て済むようになった
正確なGCを実現するためには
• 次の2つが重要
 – 生きているポインタとそうでないものを分ける

 – アロケーションしたものとその中身がなんなのかをしっ
   かり関連付ける


• 簡単に言うけど難しいよーだってCだもん
 – コンパイラ相当のものを作って、もってる型情報とか
   駆使して頑張る
サポートしています
• 配列なんかの途中を指しているポインタ
 – 効率が悪くなるから出来る限り使わない方が
   いいみたい

• 共用体
 – いまどのメンバーを使っているか記録する
 – 代入したメンバーと違うメンバーを使う行儀の
   悪いことはサポートしない。
サポートしていません
• いったん、範囲外にポインタを動かしてもう一度
  範囲内に戻す
 p = malloc(1024)
  p -= 1024
  p[1025] /* access 1024 to 2047 */
• ポインタでxorをとってみたり
 – xor linked listとか。頭いい人っているねー
• 一旦整数にcastしてまたポインタにcast
 – CRubyはgive up
• ライブラリの中でアロケーションしちゃったりとか
ローカル変数
ポインタの情報を配列に入れる                    // TRANSFORMED
                                  int cheeseburger(int* x) {
                                  void* gc_stack_frame[3];
→ プログラム変換を行う                      /* chain to previous frame: */
                                  void* last_stack_frame = GC_last_stack_frame();
                                  gc_stack_frame[0] = last_stack_frame;
                                  /* number of elements + shape category: */
                                  gc_stack_frame[1] = (1 << 2) + GC_POINTER_TYPE;
                                  /* variable address: */
 // ORIGINAL
                                  gc_stack_frame[2] = &x;
 int cheeseburger(int* x) {       /* install frame: */
                                  GC_set_stack_frame(gc_stack_frame);
 add_cheese(x);
                                  add_cheese(x);
 return x[17];                    /* restore old GC frame */
                                  GC_set_stack_frame(last_stack_frame);
 }
                                  return x[17];
                                  }
変換後プログラム拡大
// TRANSFORMED                            /* variable address: */
int cheeseburger(int* x) {                gc_stack_frame[2] = &x;

void* gc_stack_frame[3];                  /* install frame: */
                                          GC_set_stack_frame(gc_stack_frame);
/* chain to previous frame: */
                                          add_cheese(x);
void* last_stack_frame =
   GC_last_stack_frame();                 /* restore old GC frame */

gc_stack_frame[0] = last_stack_frame;     GC_set_stack_frame(last_stack_frame);
                                          return x[17];
/* number of elements + shape category:
    */                                    }

gc_stack_frame[1] = (1 << 2) +
   GC_POINTER_TYPE;
結局
• GCのシステムにどのローカル変数が生き
  ているポインタを持っているかを教える
 – コンパイラが変数の型を把握していることを利
   用
 malloc部分の変換
• mallocってこんな感じで使うよね
        malloc(sizeof(foo) * 5)
• これをみると、アロケーションするタイプ
  (foo)と数(5)が分かるわけだ。
• 正確なGCだと何の型のデータをアロケーションしたかの
  情報も必要なのでこんな感じで変換
       GC_malloc(sizeof(foo) * 5, gc_foo_tag)

• で、foo_tagってなんなの?
  – それは次のスライドの心だー
gc_foo_tagって?
• gc_tag_struct型の構造体
• アローケートしたオブジェクトと型を結びつける
• 内容はmarkとrepair(コンパクション) 処理を行う
  関数へのポインタ
 struct foo { int *x; int *y;} とするとmarkを行う関数
  void gc_mark_struct_foo (void * x_) {
      struct foo *tmp = (struct foo *) x_;
      GC_mark(tmp->x);
      GC_mark(tmp->y);
  }
 こんな関数をコンパイル時に自動生成する。
 ユーザがmark/repair関数をカスタマイズすることも可能
いろいろやってみた
       (PTL Scheme)
• PLT SchemeのGCをConservative GCから
  Precise GCにしてみた
• 最初に説明した通り、メモリ使用量が激減
• 実行速度は
  – メモリアロケーションが多いベンチマークは20%
    くらい速くなる
  – メモリアロケーションが少ないベンチマークは1
    0~20 %遅くなる 
いろいろやってみた
    (Linuxカーネル!)
• カーネルなんて絶対メモリーリークしちゃい
  かんし、GCが欲しいよね
• なんかいろいろ面倒だったみたい。続きは
  論文で
• tmpfsからddコマンドでファイルアクセス
 – GC付きのものが4割ほど遅い
•  HDDからddコマンドでファイルアクセス
 – あまり変わらない
まとめ
• この技術すげー。今後、いろんなところで
  使われるようになるはず。

• GC本第2版は保守的GCの章を書き換えな
  いといかんかもしれませんねー

• 8ccでサポート予定だから楽しみ
    http://github.com/rui314/8cc
ご清聴
   


 ありがとう
ございました

Precise garbage collection for c

  • 1.
    Precise Garbage Collectionfor C の紹介 CSNagoya 三浦英樹
  • 2.
    Precise Garbage Collectionfor C 著者  Jon Rafkind, Adam Wick, John Regehr, Matthew Flatt 入手先 http://www.cs.utah.edu/~regehr/papers/ismm15-rafkind.pdf
  • 3.
    CのプログラムでもGC使いたい • 普通、保守的 GCでしょう •たとえば、Boehm GCとか • でも、長い時間使ってるとメモリー使用量 がすごく増大する場合があるみたいだよ • 証拠は? • それは、次のスライドの心だー
  • 4.
    PLT Schemeにあった怖い話 • PLTSchemeを使用したプログラム環境 DrSchemeは毎日再起動をしなければならなかっ た。でも、正確なGCに変えたら再起動が必要なく なった • バイトコードとドキュメントを生成すると、正確な GCだと200MBで済むところが、保守的GCだと 700MBも必要だった • PLT Schemeで作ったmail clientを1日おきに再起 動していたが、正確なGCに変えたら再起動しなく て済むようになった
  • 5.
    正確なGCを実現するためには • 次の2つが重要 –生きているポインタとそうでないものを分ける – アロケーションしたものとその中身がなんなのかをしっ かり関連付ける • 簡単に言うけど難しいよーだってCだもん – コンパイラ相当のものを作って、もってる型情報とか 駆使して頑張る
  • 6.
    サポートしています • 配列なんかの途中を指しているポインタ –効率が悪くなるから出来る限り使わない方が いいみたい • 共用体 – いまどのメンバーを使っているか記録する – 代入したメンバーと違うメンバーを使う行儀の 悪いことはサポートしない。
  • 7.
    サポートしていません • いったん、範囲外にポインタを動かしてもう一度 範囲内に戻す p = malloc(1024) p -= 1024 p[1025] /* access 1024 to 2047 */ • ポインタでxorをとってみたり – xor linked listとか。頭いい人っているねー • 一旦整数にcastしてまたポインタにcast – CRubyはgive up • ライブラリの中でアロケーションしちゃったりとか
  • 8.
    ローカル変数 ポインタの情報を配列に入れる // TRANSFORMED int cheeseburger(int* x) { void* gc_stack_frame[3]; → プログラム変換を行う /* chain to previous frame: */ void* last_stack_frame = GC_last_stack_frame(); gc_stack_frame[0] = last_stack_frame; /* number of elements + shape category: */ gc_stack_frame[1] = (1 << 2) + GC_POINTER_TYPE; /* variable address: */ // ORIGINAL gc_stack_frame[2] = &x; int cheeseburger(int* x) { /* install frame: */ GC_set_stack_frame(gc_stack_frame); add_cheese(x); add_cheese(x); return x[17]; /* restore old GC frame */ GC_set_stack_frame(last_stack_frame); } return x[17]; }
  • 9.
    変換後プログラム拡大 // TRANSFORMED /* variable address: */ int cheeseburger(int* x) { gc_stack_frame[2] = &x; void* gc_stack_frame[3]; /* install frame: */ GC_set_stack_frame(gc_stack_frame); /* chain to previous frame: */ add_cheese(x); void* last_stack_frame = GC_last_stack_frame(); /* restore old GC frame */ gc_stack_frame[0] = last_stack_frame; GC_set_stack_frame(last_stack_frame); return x[17]; /* number of elements + shape category: */ } gc_stack_frame[1] = (1 << 2) + GC_POINTER_TYPE;
  • 10.
    結局 • GCのシステムにどのローカル変数が生き ているポインタを持っているかを教える – コンパイラが変数の型を把握していることを利 用
  • 11.
     malloc部分の変換 • mallocってこんな感じで使うよね malloc(sizeof(foo) * 5) • これをみると、アロケーションするタイプ (foo)と数(5)が分かるわけだ。 • 正確なGCだと何の型のデータをアロケーションしたかの 情報も必要なのでこんな感じで変換  GC_malloc(sizeof(foo) * 5, gc_foo_tag) • で、foo_tagってなんなの? – それは次のスライドの心だー
  • 12.
    gc_foo_tagって? • gc_tag_struct型の構造体 • アローケートしたオブジェクトと型を結びつける •内容はmarkとrepair(コンパクション) 処理を行う 関数へのポインタ struct foo { int *x; int *y;} とするとmarkを行う関数  void gc_mark_struct_foo (void * x_) { struct foo *tmp = (struct foo *) x_; GC_mark(tmp->x); GC_mark(tmp->y); } こんな関数をコンパイル時に自動生成する。 ユーザがmark/repair関数をカスタマイズすることも可能
  • 13.
    いろいろやってみた (PTL Scheme) • PLT SchemeのGCをConservative GCから Precise GCにしてみた • 最初に説明した通り、メモリ使用量が激減 • 実行速度は – メモリアロケーションが多いベンチマークは20% くらい速くなる – メモリアロケーションが少ないベンチマークは1 0~20 %遅くなる 
  • 14.
    いろいろやってみた (Linuxカーネル!) • カーネルなんて絶対メモリーリークしちゃい かんし、GCが欲しいよね • なんかいろいろ面倒だったみたい。続きは 論文で • tmpfsからddコマンドでファイルアクセス – GC付きのものが4割ほど遅い •  HDDからddコマンドでファイルアクセス – あまり変わらない
  • 15.
    まとめ • この技術すげー。今後、いろんなところで 使われるようになるはず。 • GC本第2版は保守的GCの章を書き換えな いといかんかもしれませんねー • 8ccでサポート予定だから楽しみ     http://github.com/rui314/8cc
  • 16.