手抜き Mark & Seep GC for D

Mark & Sweep GCアルゴリズムを適当にDで書いてみました。
頭で考えてると難しい気がするかもしれませんが、
作ってみるとそうでもないということが分かります。

  • 適当な説明

オブジェクトを作るときにobjectの連想配列に突っ込んでおきます。
ルートになるものはrootsに連想配列に突っ込んでおきます。
通常の変数の参照や、参照の削除では特別することはありません。
collectでは一度、markRootsを呼んで、rootから辿れるオブジェクトのmarkedを1にします。
終わったら、sweepを呼んで、objects内のmarkedが0なものをdeleteし、1のものは0に戻します。
最後に、rootsを空にしてcollectすると見事何もなくなります。
しっかり、循環参照している部分は消えてなくなります。
また、collectしない間はdeleteは呼ばれないことが分かると思います。
ここでは、便宜上rootsやobjectsを連想配列にしていますが、実際のGCではスピードが求められるので
ヒープ等を構成してそこから割り当てたり、削除したりといったことが必要になってくるでしょう。

以下ソースです。

class S {
	static S[S] roots;
	static S[S] objects;
	string data;
	int marked;
	S[string] children;

	this(string dt) {
		data = dt;
		printf("new %.*s\n", data);
		objects[this]=this;
	}

	~this() {
		objects.remove(this);
		printf("delete %.*s\n", data);
	}

	void opIndexAssign( S o, string i) {
		if (o is null) {
			children.remove(i);
		} else {
			children[i] = o;
		}
	}

	S opIndex(string i) {
		return children[i];
	}

	static void collect() {
		markRoots();
		sweep();
	}

	static void markRoots() {
		foreach (S s; roots) {
			mark(s);
		}
	}

	static void sweep() {
		foreach (S s; objects) {
			if (s.marked == 0) delete s;
			else s.marked = 0;
		}
	}

	static void mark(S s) {
		if (s.marked == 0) {
			s.marked = 1;
			foreach(S c; s.children) {
				mark(c);
			}
		}
	}
}

void main() {
	S t = new S("root");
	S.roots[t] = t;
	t["a"] = new S("a");
	t["b"] = new S("b");
printf("remove root\n");
	t["a"] = null;
	t["b"] = null;
printf("ok\n");
printf("increment root 2\n");
	t["a"] = new S("c");
	t["a"]["a"] = new S("d");
	t["a"]["a"]["a"] = new S("e");
printf("--\n");
printf("increment root 3\n");
	t["b"] = new S("f");
	t["b"]["a"] = new S("g");
	t["b"]["a"]["a"] = t["b"];
printf("make cycle ref\n");
	t["b"] = null;
printf("collect\n");
	S.collect();
printf("remove root & collect\n");
	S.roots.remove(t);
printf("collect\n");
	S.collect();
}