B演習(言語処理系演習)第9回 メモリ管理・ごみ集め 田浦 我々の処理系はmalloc/newを使って いる 字句解析器 構文解析結果(構文木) 関数を呼び出すたびに作られる「環境」 式を評価した結果 • 例: 2.3 + 3.4, [ 1, 2, 3 ], { a : b }, … 復習: C/C++言語で代表的な3種類の メモリ割り当て方法 局所変数・配列 関数呼び出し終了と共に使えな おそらく間違い くなる • int* f() { int a[1000]; …; return a; } • double* g() { double f; …; return &f; } 大域変数・配列 プログラム実行中常に使える.し かし,実行中に「追加割り当て」できない ヒープ : OSから実行時に取得.代表API : malloc (C), new (C++), … • 注 : 中でmallocを呼ぶ関数(e.g., strdup)もある 基本フォーム 関数呼び出し後も使いたいメモリ 局所変数・ 配列の領域は使うな 何バイト確保すべきか,同時にいくつ確保す べきかわからない 大域変数・配列の領域は 使うな そのような時はmalloc/newを使う しかしmalloc/newされた領域をいつfree/delete すべきかはプログラマの責任… 割り当てたメモリは「いらなくなったら」 解放しなくてはならない いつ「いらなくなる」のか あまり気にする必要ないもの • 字句解析器 • 構文解析結果(構文木) 大いに問題となるもの • 「環境」 • 式を評価した結果 実験: mallocしっぱなしだと何が起こる か GC (Garbage Collection) あるメモリ領域が「いらなくなった」かどうか, 自動的に見つけていらなければ解放(再利 用)する仕組み Java, Python, Perl, VBA, … (最近の)言語に は必ずといっていいほど備わっている C/C++にはそれがないのだが… 天の助け GC ライブラリ http://www.hpl.hp.com/personal/Hans_Boehm /gc/ • (Ubuntu) apt-get install libgc-dev C/C++プログラムのmallocをGC_MALLOCに 置き換え, freeを除去するだけで再コンパイル &リンクをするだけで,自動的にいらないメモ リを解放してくれるライブラリ 演習HP「各種情報」参照 GCの原理 GC以前のメモリ管理の基本原理 メモリ管理モジュールが「現在使われている/ いない領域」を把握するデータ構造を構築 割り当て(e.g., malloc(100)) • 使われていない領域から,要求サイズを満たす 領域を探索 解放(e.g., free(p)) • 解放された領域を「使われていない領域」として 記録(以降のmallocで利用) メモリ管理データ構造の例 空き領域リスト(フリーリスト) 使用中アドレス サイズの表 free (例: ハッシュ表) 空き領域 使用中領域 例(続) 空き領域リストのデータ表現 typedef struct free_region * free_region_t; typedef struct free_region{ free_region_t next; int sz; } free_region; malloc(sz) { 空き領域リストをたどって要求szを満たす regionを検索; } GCなしのメモリ管理の危険 メモリ管理ライブラリは盲目的にユーザプログラム (malloc/freeの呼び出し)を信ずる 早すぎるfree • freeしてはいけないものをfree • 本来別のアドレスに割り当てられるべき複数の領域が同 じアドレスに割り当てられる意図しないデータの破壊 メモリリーク • freeすべきものをfreeしない • 本来他の目的に使われるべきアドレスが再利用されない 意図しない使用メモリ領域の増大 自動メモリ管理(ごみ集め; Garbage Collection) 「もう使われない領域」を自動的に発見して解 放 プログラムの安全性は格段に向上する GC用語 • (プログラム実行中のある時点で) 「生きている領 域」 = その時点以降使われる(アクセスされる)領 域 • 反対語: 「死んでいる領域」 もう使われない領域をどう発見するの か? (実現不可能な)理想 プログラムを実行し続けた 際に,今後使われる領域とそうでない領域を各時点 で100%正しく判定する(一種の未来予知? アルゴリ ズム自動理解?) main() この地点以降使われるメモリ領域は { a[0], a[1], a[4], a[9], a[16], … …; for (i = 0; i < n; i++) { p = a[i * i]; … *p … } } 実際のGCが行っている「近似」 現在局所・大域変数に入っているアドレス(が 指すメモリ領域)は「生きている」 • 注: アドレスが指す「メモリ領域」 そのアドレスを 含む,一回のメモリ割り当てで割り当てられた領 域 ある「使われる」メモリ領域中に入っているア ドレス(が指すメモリ領域)は「生きている」 • 配列の要素,構造体のフィールドなど 「今後使われる」 「生きている」 同じことの書き換え LIVE : (ある注目している時点で)「生きてい る」アドレスの集合 ある局所・大域変数の値 = a a LIVE “a LIVE” かつ “pが,aが指すメモリ領域内 に格納されている” p LIVE a p 要するに局所・大域変数から「到達可 能」なものが「生きている」 局所変数 大域変数 GCの基本原理 割り当て (e.g., malloc(sz);) • 空き領域からszバイト分の連続した空き領域(a)を発見 • [a, a + sz) を使用中と記録 (a をキーとして探索するとszが 分かるよう,何らかのデータ構造に記録しておく) このとき,空き領域が足りなくなったら(基準は様々), • 1. 生きている領域をすべて見つける(マーク) • 2. 残りの領域を解放(スイープ) マーク 生きているものすべてに「印」をつける • 印の場所: オブジェクトの先頭に専用の領域を 作っておく,ハッシュ表などを作る,などがある 要するにグラフの探索問題(例えばdepthfirst) 擬似コード: • void GC() { mark_stack = mk_empty_mark_stack(); initialize all memory blocks as `not marked’; for each word p in global variable { mark(p); } for each word p in local variable { mark(p); } while (! empty(mark_stack)) { p = pop (mark_stack); for each word q in the memory block pointed to by p { mark(q); } } for all memory block p not marked { free(p); } } void mark(p) { if (! marked(p)) { record p as `marked’; push(p, mark_stack); }} 「mini-Pythonに自力GCを実装」の チャレンジャーへ 最低限必要なこと • アドレスa aが割り当て中領域であるかどうか が判定できる.そうならばそのサイズ • 割り当てた領域のアドレスをすべて列挙できる • 全局所変数中のポインタの列挙 • 全大域変数中のポインタの列挙 • あるアドレスaで指されている領域中のポインタの 列挙 チャレンジャー用tips 要領1: 空き領域管理は,それ自身重労働.こ れをmallocにまかせると楽かもしれない • my_GC_malloc(sz) { a = malloc(sz); aからszバイト割り当て中と記録; return a; application } my_GC_malloc my_GC_malloc malloc malloc/free free チャレンジャー用tips 要領2 : 主たる手間は「割り当て中領域」を記 録するデータ構造 おそらくハッシュ表を作るのが良いだろう • キー : アドレス • 値 : 割り当てサイズ + 「印」 注: ポインタとしてありえない値(最下位2bitが 0でない)はハッシュ表を引くまでもない 全局所変数の列挙 局所変数はスタックに格納されている.ごく一 部はレジスタ スタック: main関数における局所変数のアドレ スとGCが起動されたときの局所変数のアドレ スに挟まれた領域 レジスタ上の値については,挑戦したい人は 質問してください(try “man setjmp”) main interpreter eval_... GC() (ほぼすべての)局所変数 大域変数の列挙 インタプリタ実装時に,大域変数を使わない(大域変 数にポインタを置かない)のが一番簡単 使う場合は, • 「大域変数のアドレスをGCに登録するAPI」を用意すると 良いかもしれない • int * global_var; GC_register_root(&global_var, sizeof(int)); 奥の手については,チャレンジする人は質問してく ださい GCはいつ起動すべきか ×mallocが成功する限り起動しない • メモリ使用量が多くなりすぎる 自らある制限(使用中メモリサイズ <= A)を課 す Aをいくらにするか? • GC終了後に「生きている量」を測り,その最大値 ×定数(2~3)とするのが基本
© Copyright 2024 ExpyDoc