More Related Content
Similar to マーク&スイープ勉強会 (20)
マーク&スイープ勉強会
- 12. 変数探し
• スタックフレームを走査して変数を検出
#include <stdio.h>
void search(int *start, int value) {
for (int *p = start; p > &value; p--) {
if (*p == value) {
printf("found: %p¥n", p);
return;
}
}
}
int main(int argc, char *argv[]) {
int a = 0xcafebabe;
printf("&a: %p¥n", &a);
search(&argc, a);
}
&a: 0028FF1C
found: 0028FF1C
実行結果
- 13. malloc (1)
• 冒頭の問題に戻る
#include <stdlib.h>
int main() {
void *a = malloc(10);
void *b = malloc(10);
a = NULL;
}
• malloc() の戻り値を記録しておいて、スタック
フレームに含まれるか確認すれば良い
- 14. malloc (2)
• malloc() の戻り値を記録する例
#include <stdlib.h>
#include <map>
static std::map<void *, bool> mallocs;
void *gc_malloc(size_t size) {
void *ret = malloc(size);
mallocs[ret] = true;
return ret;
}
- 15. 問題 1
• 以下のコードで使われている gc_init(),
gc_malloc(), gc_collect() を実装してください。
• ※ 最初に掲示した問題を改変しました。
#include <stdlib.h>
int main(int argc, char *argv[]) {
gc_init(&argc);
void *a = gc_malloc(10);
void *b = gc_malloc(10);
a = NULL;
gc_collect();
}
- 16. 問題 1 の回答例
static void **stack_start;
void gc_init(int *pargc) {
stack_start = reinterpret_cast<void **>(pargc);
}
void gc_collect() {
void *end;
for (auto it = mallocs.begin(); it != mallocs.end(); ++it)
it->second = false;
for (auto p = stack_start; p > &end; p--)
if (mallocs.find(*p) != mallocs.end())
mallocs[*p] = true;
for (auto it = mallocs.begin(); it != mallocs.end();)
if (it->second) ++it; else {
free(it->first);
mallocs.erase(it++);
}
}
- 20. 問題 2
• 生きている領域が解放されないようにGCを
修正してください。
#include <stdlib.h>
struct Test { void *ptr; };
int main(int argc, char *argv[]) {
gc_init(&argc);
Test *t = reinterpret_cast<Test *>(
gc_malloc(sizeof(Test)));
t->ptr = gc_malloc(10);
gc_collect();
} 生きているのに
回収されてしまう!
- 21. 問題 2 の回答例(抜粋)
struct HeapInfo { size_t size, sizep; bool marked; };
static std::map<void *, HeapInfo> mallocs;
void *gc_malloc(size_t size) {
void *ret = malloc(size);
HeapInfo hi = { size, size / sizeof(void *), true };
mallocs[ret] = hi;
return ret;
}
static void gc_scan(void **start, void **end) {
for (auto p = start; p < end; p++) {
auto it = mallocs.find(*p);
if (it != mallocs.end() && !it->second.marked) {
it->second.marked = true;
auto pp = reinterpret_cast<void **>(*p);
gc_scan(pp, pp + it->second.sizep);
}
}
}
- 22. 問題 3
• 無駄なスキャンを抑制してください。
struct Test { void *ptr; int *buf; };
int main(int argc, char *argv[]) {
gc_init(&argc);
Test *t = reinterpret_cast<Test *>(
gc_malloc(sizeof(Test)));
t->ptr = gc_malloc(10);
t->buf = reinterpret_cast<int *>(
gc_malloc(1024 * 1024));
gc_collect();
} ポインタが含まれていない
内部の走査は無駄!
- 23. 問題 3 の回答例(抜粋)
• 内部を走査するか指定できるようにする
struct HeapInfo { size_t size, sizep; bool marked, scan; };
void *gc_malloc(size_t size, bool scan = true) { 略 }
static void gc_scan(void **start, void **end) {
略
it->second.marked = true;
if (it->second.scan) { 略 }
略
}
int main(int argc, char *argv[]) {
略
t->buf = reinterpret_cast<int *>(
gc_malloc(1024 * 1024, false));
gc_collect();
}
- 25. new (1)
• 以前出てきた例を改めて見返すと・・・
#include <stdlib.h>
struct Test { void *ptr; };
int main(int argc, char *argv[]) {
gc_init(&argc);
Test *t = reinterpret_cast<Test *>(
gc_malloc(sizeof(Test)));
t->ptr = gc_malloc(10);
gc_collect();
} いくら何でも冗長過ぎる!
newで簡単に書けないか?
- 26. new (2)
• operator newを置き換えると無条件にGC対
象となってしまうため、意図的にGCを外した
いときに不便
• newした後、GCに登録する関数を用意
• 常にGC対象にしたいクラスはコンストラクタ
で自分を登録すれば良い
Test *t = gc_register(new Test);
struct Test {
Test() { gc_register(this); }
};
- 27. new (3)
• deleteで解放するため、解放処理をコール
バックとして登録できるようにする
• free() と同じ型の関数ポインタで登録するた
め、delete のラッパーを用意
template <class T> void gc_delete(void *p) {
delete reinterpret_cast<T *>(p);
}
template <class T> void gc_delete_array(void *p) {
delete [] reinterpret_cast<T *>(p);
}
- 28. 実装例(抜粋)
struct HeapInfo {
size_t size, sizep; bool marked, scan;
void (*free)(void *);
};
void gc_register(void *p, size_t size, bool scan,
void (*free)(void *)) {
HeapInfo hi = { size, size / sizeof(void *),
true, scan, free };
mallocs[p] = hi;
}
void *gc_malloc(size_t size, bool scan = true) {
void *ret = malloc(size);
gc_register(ret, size, scan, free);
return ret;
}
template <class T> T *gc_register(T *t) {
gc_register(t, sizeof(T), true, gc_delete<T>);
return t;
}
- 31. 実装例(抜粋)
static std::list<void **> gc_ptrs;
template <class T> class gc_ptr {
T *ptr;
std::list<void **>::iterator it;
void init(T *p) {
ptr = p;
gc_ptrs.push_back(reinterpret_cast<void **>(&ptr));
it = --gc_ptrs.end();
}
public:
gc_ptr() { init(NULL); }
gc_ptr(const gc_ptr<T> &p) { init(p.ptr); }
gc_ptr(T *p) { init(p); }
~gc_ptr() { gc_ptrs.erase(it); }
gc_ptr<T> &operator =(T *p) { ptr = p; return *this; }
inline operator T *() const { return ptr; }
};
- 34. クロージャ (3)
• ヒープを使う方式は、ローカル変数のキャプ
チャに参照を使う F# と同じ構造
• ヒープへのポインタを値束縛することで、環境
を閉じ込めたように見せかけている
std::function<int()> test() {
gc_ptr<int> a = gc_new<int>();
*a = 0;
return [=] { return ++(*a); };
}
C++ with GC
let test () =
let a = ref 0
fun () -> a := !a + 1; !a
F#
- 36. 閾値 (1)
• 今までは手動で gc_collect を呼んでいた
• GCに登録するときに使用メモリ量をカウント
して、閾値を超えたら呼ぶことで自動化
static size_t mem_used;
static size_t threshold = 1024 * 1024;
void gc_register(void *p, size_t size, bool scan,
void (*free)(void *)) {
HeapInfo hi = { size, size / sizeof(void *),
true, scan, free };
mallocs[p] = hi;
mem_used += size;
if (mem_used > threshold) gc_collect();
}
- 37. 閾値 (2)
• スイープの際に使用メモリ量を減らす
• 最終的に確定した使用量で閾値を調整
void gc_collect(void *dummy) {
略
for (auto it = mallocs.begin(); it != mallocs.end();) {
if (it->second.marked) ++it; else {
printf("sweep: %p¥n", it->first);
(*it->second.free)(it->first);
mem_used -= it->second.size;
mallocs.erase(it++);
}
}
while (mem_used > (threshold >> 1))
threshold <<= 1;
}
- 38. 閾値 (3)
• 自動的に gc_collect() が呼ばれるようになる
int main(int argc, char *argv[]) {
gc_init(&argc);
for (int i = 0; i < 10; i++)
gc_malloc(300 * 1024, false);
}
• 処理内容によってGCの特性は異なる
– 巨大なサイズが少数確保される場合
– 小さいサイズが大量に確保される場合
• サイズや個数も考慮すると効率が上がる