Elisp 標記-清除算法簡介
標記-清除(mark-sweep)算法
Emacs Lisp最早使用的就是標記清除算法. 算法分爲"標記"和"清除"兩個階段.
1) 首先標記出所有正在使用的對象.
2) 回收那些所有未被標記的對象,並清除掉標記.
該算法的缺點是:
1) 效率問題, 標記和清除的效率並不高.
2) GC運行時, 正常程序必須停下來, 降低實時性.
下面以elisp中的cons單元的爲例來釋放標記-清除的算法.
elisp中的所有對象是 lisp_object.
typedef lisp_object {
unsigned int val;
unsigned int type:7;
unsigned int mark:1;
} lisp_object;
其實lisp_object是個聯合體(union), 這裏只描述lisp_object中的GC相關的部分.
type : 表示該lisp_object的類型, 有符號,函數,cons,或者字符串等等.
val : 表示該lisp_object的值, 一般來說是相應類型的指針.
mark : mark-sweep算法中使用, 表示該對象是否被標記. 未被標記的對象代表着不會被引用到,要回收.
cons的結構體相對比較簡單點,就包含car和cdr兩個對象.
struct lisp_cons {
lisp_ojbect car, cdr;
};
瞭解了cons和lisp_object對象結構之後, 我們還需要知道cons的內存申請,標記和回收.
一. cons的內存申請步驟:
1)查看空閒的cons鏈表, 爲了節省空間把car當做鏈表指針. 如果不爲空的話,則直接從空閒鏈表中取出一個cons單元
2)如果空閒鏈表爲空, 則從cons塊中申請一個cons單元. 一個cons塊有500個cons單元, 每個塊形成一個鏈表.
cons函數實現如下:
lisp_object Fcons(lisp_object car, libp_object cdr) {
lisp_object val;
if (cons_free_list) {
// 設置lisp_object的類型爲cons, 值爲cons_free_list的指針
XSET(val, Lisp_Cons, cons_free_list);
// cons_free_list 指向鏈表的下一個地址
cons_free_list = (struct Lisp_Cons *) XFASTINT (cons_free_list->car);
} else {
// cons_block_index 爲cons塊使用的索引, cons_block爲當前正在使用的cons塊
if (cons_block_index == CONS_BLOCK_SIZE) {
struct cons_block *new = (struct cons_block *) malloc (sizeof (struct cons_block));
if (!new) memory_full ();
new->next = cons_block;
cons_block = new;
cons_block_index = 0;
}
XSET (val, Lisp_Cons, &cons_block->conses[cons_block_index++]);
}
XCONS (val)->car = car;
XCONS (val)->cdr = cdr;
return val;
}
二. cons塊的標記. lisp的GC要標記出所有可達的對象, 就必須記錄那些全局的對象.根據全局對象對其他對象的引用鏈來標記出所有的引用對象, 這樣那些申請的,而沒被標記出的對象就是可回收的. 下面來看一下標記函數如何來標記cons對象的.
void mark_object(lisp_object *objptr) {
switch (XGCTYPE (obj))
case Lisp_Cons : {
struct Lisp_Cons *ptr = XCONS (obj);
if (XMARKBIT (ptr->car)) break;
XMARK (ptr->car);
mark_object(&ptr->car);
mark_object (&ptr->cdr);
break;
/* other object */
case ....:
break;
}
}
三. 標記出所有cons的可達對象之後,就需要掃描所有的被申請的cons對象, 並把那些未標記的cons對象添加的cons空閒鏈表中.
void sweep_cons(void) {
struct cons_block *cblk;
int lim = cons_block_index;
int num_free = 0, num_used = 0;
cons_free_list = 0;
// 掃描所有的空閒塊
for (cblk = cons_block; cblk; cblk = cblk->next) {
int i;
// 掃描空閒塊中的所有cons單元, 查看其是否被標記
for (i = 0; i < lim; i++) {
if (!XMARKBIT (cblk->conses[i].car)) {
XFASTINT (cblk->conses[i].car) = (int) cons_free_list;
num_free++;
cons_free_list = &cblk->conses[i];
} else {
num_used++;
XUNMARK (cblk->conses[i].car);
}
}
lim = CONS_BLOCK_SIZE;
}
// 統計使用和未使用的cons單元數量
total_conses = num_used;
total_free_conses = num_free;
}
至於elisp其他類型的對象也依此類推, 不過是標記的地方不一樣了而已. 總體來說標記-清除算法還是比較簡單的, 不過越是簡單的東西越是基礎, 掌握的這個算法之後我們就可以進階其他的改進GC算法了.
本文參考了Emacs-18.59的代碼, 舊版本的Emacs代碼可以從這個網站上下載: ftp://ftp.gnu.org/old-gnu/emacs/.
之所以選擇舊版本代碼閱讀, 是因爲舊版本的代碼比較少,功能簡單, 核心思想卻於新版本差不多.
Emacs Lisp最早使用的就是標記清除算法. 算法分爲"標記"和"清除"兩個階段.
1) 首先標記出所有正在使用的對象.
2) 回收那些所有未被標記的對象,並清除掉標記.
該算法的缺點是:
1) 效率問題, 標記和清除的效率並不高.
2) GC運行時, 正常程序必須停下來, 降低實時性.
下面以elisp中的cons單元的爲例來釋放標記-清除的算法.
elisp中的所有對象是 lisp_object.
typedef lisp_object {
unsigned int val;
unsigned int type:7;
unsigned int mark:1;
} lisp_object;
其實lisp_object是個聯合體(union), 這裏只描述lisp_object中的GC相關的部分.
type : 表示該lisp_object的類型, 有符號,函數,cons,或者字符串等等.
val : 表示該lisp_object的值, 一般來說是相應類型的指針.
mark : mark-sweep算法中使用, 表示該對象是否被標記. 未被標記的對象代表着不會被引用到,要回收.
cons的結構體相對比較簡單點,就包含car和cdr兩個對象.
struct lisp_cons {
lisp_ojbect car, cdr;
};
瞭解了cons和lisp_object對象結構之後, 我們還需要知道cons的內存申請,標記和回收.
一. cons的內存申請步驟:
1)查看空閒的cons鏈表, 爲了節省空間把car當做鏈表指針. 如果不爲空的話,則直接從空閒鏈表中取出一個cons單元
2)如果空閒鏈表爲空, 則從cons塊中申請一個cons單元. 一個cons塊有500個cons單元, 每個塊形成一個鏈表.
cons函數實現如下:
lisp_object Fcons(lisp_object car, libp_object cdr) {
lisp_object val;
if (cons_free_list) {
// 設置lisp_object的類型爲cons, 值爲cons_free_list的指針
XSET(val, Lisp_Cons, cons_free_list);
// cons_free_list 指向鏈表的下一個地址
cons_free_list = (struct Lisp_Cons *) XFASTINT (cons_free_list->car);
} else {
// cons_block_index 爲cons塊使用的索引, cons_block爲當前正在使用的cons塊
if (cons_block_index == CONS_BLOCK_SIZE) {
struct cons_block *new = (struct cons_block *) malloc (sizeof (struct cons_block));
if (!new) memory_full ();
new->next = cons_block;
cons_block = new;
cons_block_index = 0;
}
XSET (val, Lisp_Cons, &cons_block->conses[cons_block_index++]);
}
XCONS (val)->car = car;
XCONS (val)->cdr = cdr;
return val;
}
二. cons塊的標記. lisp的GC要標記出所有可達的對象, 就必須記錄那些全局的對象.根據全局對象對其他對象的引用鏈來標記出所有的引用對象, 這樣那些申請的,而沒被標記出的對象就是可回收的. 下面來看一下標記函數如何來標記cons對象的.
void mark_object(lisp_object *objptr) {
switch (XGCTYPE (obj))
case Lisp_Cons : {
struct Lisp_Cons *ptr = XCONS (obj);
if (XMARKBIT (ptr->car)) break;
XMARK (ptr->car);
mark_object(&ptr->car);
mark_object (&ptr->cdr);
break;
/* other object */
case ....:
break;
}
}
三. 標記出所有cons的可達對象之後,就需要掃描所有的被申請的cons對象, 並把那些未標記的cons對象添加的cons空閒鏈表中.
void sweep_cons(void) {
struct cons_block *cblk;
int lim = cons_block_index;
int num_free = 0, num_used = 0;
cons_free_list = 0;
// 掃描所有的空閒塊
for (cblk = cons_block; cblk; cblk = cblk->next) {
int i;
// 掃描空閒塊中的所有cons單元, 查看其是否被標記
for (i = 0; i < lim; i++) {
if (!XMARKBIT (cblk->conses[i].car)) {
XFASTINT (cblk->conses[i].car) = (int) cons_free_list;
num_free++;
cons_free_list = &cblk->conses[i];
} else {
num_used++;
XUNMARK (cblk->conses[i].car);
}
}
lim = CONS_BLOCK_SIZE;
}
// 統計使用和未使用的cons單元數量
total_conses = num_used;
total_free_conses = num_free;
}
至於elisp其他類型的對象也依此類推, 不過是標記的地方不一樣了而已. 總體來說標記-清除算法還是比較簡單的, 不過越是簡單的東西越是基礎, 掌握的這個算法之後我們就可以進階其他的改進GC算法了.
本文參考了Emacs-18.59的代碼, 舊版本的Emacs代碼可以從這個網站上下載: ftp://ftp.gnu.org/old-gnu/emacs/.
之所以選擇舊版本代碼閱讀, 是因爲舊版本的代碼比較少,功能簡單, 核心思想卻於新版本差不多.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.