lua_gc 源碼學習二

來源:互聯網
上載者:User

標籤:

普及下常識:GC 是 garbage collector 資源回收器;

初期的 Lua GC 採取的是 stop the world 的實現。一旦產生 gc 就需要期待全部 gc 流程走完。lua 自己是個很精簡的體系,但不代表處理的資料量也必然很小。

從 Lua 5.1 入手下手,GC 的實現改成分步的。固然照舊是 stop the world ,可是,每個步驟均可以分階段執行。這樣,屢次擱淺的時間較小。隨之,這部門的代碼也相對於紛亂了。分步執行最關鍵的問題是需要處理在 GC 的步驟之間,如果資料關聯的狀態發生了變化,如何保證 GC 的準確性。GC 的分步執行相對一次執行完,總的時候開銷的不同並非零代價的。只是在實現上,要儘可能讓額外增長的價錢較小。

先來看 GC 流程的分別。

lua 的 GC 分為五個大的階段GC 處於哪一個階段(代碼中被稱為狀態),根據的是global_State中的 gcstate 域。狀態以宏式樣界說在 lgc.h 的 14 行。

/*** Possible states of the Garbage Collector*/#define GCSpause    0#define GCSpropagate    1#define GCSsweepstring  2#define GCSsweep    3#define GCSfinalize 4

狀態的值的巨細也表示著它們的履行順序。需要注重的是,GC 的執行曆程並不是每程式都堵塞在一個狀況上。

GCSpause 階段是每一個 GC 流程的啟始步調。僅僅標識表記標幟體系的根節點。見 lgc.c 的 561 行。

switch (g->gcstate) 
{    case GCSpause: markroot(L);  /* start a new collection */      return 0;

markroot 這個函數所做之事,就是標記主線程對象,標記主線程的局部表、註冊表,以及為局部範例註冊的元表。標記的詳細過程我們背面再講。

GCSpause 階段執行完,立即就將狀態切換到了 GCSpropagate 。這是一個標記流程。這個流程會分步結束。當檢測到另有對象待標記時,迭代標記(重複調用 propagatemark);終究,會有一個標記功程不可被打斷,這些操縱放在一個叫作 atomic 的函數中執行。見 lgc.c 的 565 行:

case GCSpropagate: 
{      if (g->gray)        return propagatemark(g);      else /* no more `gray‘ objects */        atomic(L);  /* finish mark phase */        return 0;}

這裡大概需要順帶提一下的是 gray 域。望文生義,它指 GCObject 中的灰色節點鏈。作為灰色,即處於白色和黑色之間的狀態。對於節點的顏色,頓時就會闡明。

接下來就是清除流程了。

前面我們提到過,string 在 Lua 中是單獨經管的,以是也需要單獨清除。GCSsweepstring 階段乾的就是這個事。string table 以 hash 表形式治理所有的 string 。GCSsweepstring 中,每個步驟(step) 清理 hash 表的一列。代碼見 lgc.c 的 573 行

case GCSsweepstring: lu_mem old = g->totalbytes;      sweepwholelist(L, &g->strt.hash[g->sweepstrgc++]);      if (g->sweepstrgc >= g->strt.size)  /* nothing more to sweep? */        g->gcstate = GCSsweep;  /* end sweep-string phase */      lua_assert(old >= g->totalbytes);      g->estimate -= old - g->totalbytes;      return GCSWEEPCOST;

這裡可以看到 estimate 和 totalbytes 兩個域,從名字上可以知道,它們別離暗示了 lua vm 佔用的記憶體位元組數以及實際指派的位元組數。

ps. 如果你本身實現過記憶體管理器,當知道記憶體治理自己是有分外的記憶體開銷的。如果有需要確切控制記憶體數目,我偏向於連續記憶體管理器統計精確的記憶體使用量環境。比如你向記憶體管理器索取 8 位元組記憶體,實際的記憶體開銷極可能是 12 位元組,乃至更多。如果想做這方面的點竄,讓 lua 的 gc 能更真正的反映記憶體實際使用情形,保舉修改 lmem.c 的 76 行,luaM_realloc_函數。所有的 lua 中的記憶體使用量改變城市經由過程這個函數。

從下面這段代碼中,我們還見到了 GCSWEEPCOST 這個秘密數字。這個數字用於節制 GC 的進度。這超越了本日的話題。留待今後闡發。

接下來就是對所有未標記的別的 GCObject 做清理勞動了。即 GCSsweep 階段。它和上面的 GCSsweepstring 近似。

最終是 GCSfinalize 階段。假如在前方的階段發現了需要調用 gc 元方法的 userdata 對象,將在這個階段逐一調用。做這件工作的函數是 GCTM 。

後面已談到過,全部具有 gc 元要領的 userdata 對象和其聯絡關係的資料,現實上都不會在以前的斷根階段被肅清。(因為零丁做過標誌)所有的元方法挪用都是平安的。而它們的現實排除,則需比及下一次 GC 流程了。或是在lua_close被調曆時消滅。

ps.lua_close其實不做完全的 gc 事情,只是簡單的處置懲罰所有 userdata 的 gc 元方法,以及開釋所有效到的記憶體。它是相對於便宜的。

接下來我們看看 GC 標記流程觸及的一些觀點。

簡略的說,lua 以為每一個 GCObject (需要被 GC 搜集器辦理的工具)都有一個色彩。一起頭,一切節點都是白色的。建立立進去的節點也被默許配置為紅色。

在標記階段,可見的節點,逐個被設定為玄色。有些節點比較龐大,它會聯絡關係此外節點。再不處置完所相關聯節點前,lua 以為它是灰色的。

節點的顏色被貯存在 GCObject 的 CommonHeader 裡,放在 marked 域中。為了節儉記憶體,因此用位寄存。marked 是一個單位元組量。統共可以儲存 8 個標記。而 lua 5.1.4 用到了 7 個標記位。在 lgc.h 的 41 行,有其詮釋:

/*** Layout for bit use in `marked‘ field:** bit 0 - object is white (type 0)** bit 1 - object is white (type 1)** bit 2 - object is black** bit 3 - for userdata: has been finalized** bit 3 - for tables: has weak keys** bit 4 - for tables: has weak values** bit 5 - object is fixed (should not be collected)** bit 6 - object is "super" fixed (only the main thread)*/#define WHITE0BIT   0#define WHITE1BIT   1#define BLACKBIT    2#define FINALIZEDBIT    3#define KEYWEAKBIT  3#define VALUEWEAKBIT    4#define FIXEDBIT    5#define SFIXEDBIT   6#define WHITEBITS   bit2mask(WHITE0BIT, WHITE1BIT)

lua 定義了一組宏來操縱這些標記位,代碼就再也不列出。只要要翻開 lgc.h 就可以很輕快的明白這些宏的函數。

白色和黑色是劃分標記的。當一個對象非白非黑時,就認為它是灰色的。

為何有兩個白色符號位?這是 lua 採取的一個小技能。在 GC 的標誌流程完成,但清算流程還沒有作完前。一旦對象間的干係產生轉變,好比新增添了一個對象。這些對象的生命期是不能預知的。最安定的方法是把它們標記為不可消除的。由於清理進程結束,需要把所有對象設定回白色,便利下次清算。lua 其實是單遍掃描,處置完一個節點就重設一個節點的顏色的。簡樸的把新建立出來的對象設定為黑,有可能致使它在 GC 流程竣事後,再也沒機遇變回白色了。

簡略的方法便是設定從第三種狀態。也便是第 2 種白色。

在 Lua 中,兩個白色狀態是一個乒乓開關,當前需要刪除 0 型白色節點時, 1 型白色節點就是被保護起來的;反之也同樣。

以後的白色是 0 型仍是 1 型,見global_State的 currentwhite 域。otherwhite() 用於乒乓切換。獲適當前白色狀態,利用定義在 lgcc.h 77 行的宏:

#define luaC_white(g)   cast(lu_byte, (g)->currentwhite & WHITEBITS)

FINALIZEDBIT 用於標記 userdata 。當 userdata 確定不被援用,則設定上這個標記。它分歧於顏色標記。因為 userdata 由於 gc 元方法的存在,釋放所佔記憶體是需要放到 gc 元方法調用以後的。這個標記可以包管元方法不會被頻頻調用。

KEYWEAKBIT 和 VALUEWEAKBIT 用於標記 table 的 weak 屬性,無需多言。

FIXEDBIT 可以保證一個 GCObject 不會在 GC 流程中被清除。為什麼要有這類狀態?關鍵在於 lua 本身會用到一個字串,它們有可能不被任何處所引用,但在屢次用到這個字串時。那麼,這些字串就會被珍愛起來,設定上 FIXEDBIT ,Mac下主動殺死ssh曆程並重連

在 lstring.h 的 24 行界說有:

#define luaS_fix(s) l_setbit((s)->tsv.marked, FIXEDBIT)

能夠把一個字串配置為被庇護的。

典範的運用場所見 llex.c 的 64 行:

void luaX_init (lua_State *L) {  int i;  for (i=0; i<NUM_RESERVED; i++) 
TString *ts="luaS_new(L," luaX_tokens[i]); 
luaS_fix(ts); 
reserved words are never collected * lua_assert(strlen(luaX_tokens[i])+1 <="TOKEN_LEN);" ts->tsv.reserved = cast_byte(i+1);  /* reserved word */}

以及 ltm.c 的 30 行:

void luaT_init (lua_State *L) {  static const char *const luaT_eventname[] = /* ORDER TM */    "__index", "__newindex",    "__gc", "__mode", "__eq",    "__add", "__sub", "__mul", "__div", "__mod",    "__pow", "__unm", "__len", "__lt", "__le",    "__concat", "__call";  int i;  for (i=0; itmname[i] = luaS_new(L, luaT_eventname[i]);    luaS_fix(G(L)->tmname[i]);  /* never collect these names */}

以元方式為例,若是咱們操縱 lua 尺度 api 來摹擬 metatable 的行動,就不能寫的和原生的 meta 機制高效。由於,當我們取到一個 table 的 key ,想知道它是否是__index時,要末我們需要挪用 strcmp 做比力;要麼利用lua_pushlstring先將需要比較的 string 壓入lua_State,而後再比較。

咱們曉得 lua 中值分歧的 string 同享了一個 string 工具,即 TString 地點是一概的。對照兩個 lua string 的價格是很小(只需求比力一個指標),比 C 函數 strcmp 高效。但lua_pushlstring卻有額外開消。它必要去計較 hash 值,盤問 hash 表 (string table) 。

lua 的 GC 演算法並不做記憶體清算,它不會在記憶體中遷徙資料。實際上,如果你能確定一個 string 不會被清除,那末它的記憶體地點也是穩定的,這樣就帶來的最佳化空間。ltm.c 中就是如許做的。

見 lstate.c 的 93 行:

TString *tmname[TM_N];  /* array with tag-method names */

global_State中 tmname 域就乾脆以 TString 指標的體例記實了所有元方法的名字。換作準則的 lua api 來做的話,凡是我們需要把這些 string 放到註冊表,或情況表中,才幹擔保其不被 gc 清除,且可以在比較時拿到。lua 本身的完成則哄騙 FIXEDBIT 做了一步最佳化。

最後,我們來看看 SFIXEDBIT 。實在它的用處只要一個,就是標記主 mainthread 。也就是一概的出發點。我們調用lua_newstate返回的阿誰布局。

為甚麼需要把這個布局特別看待?因為即便到lua_close的那一刻,這個構造也是不克隨便清除的。我們來看看天下末日時,法式都履行了什嗎?見 lstate.c 的 105 行。

static void close_state (lua_State *L) global_State *g = G(L);  luaF_close(L, L->stack);  /* close all upvalues for this thread */  luaC_freeall(L);  /* collect all objects */  lua_assert(g->rootgc == obj2gco(L));  lua_assert(g->strt.nuse == 0);  luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size, TString *);  luaZ_freebuffer(L, &g->buff);  freestack(L, L);  lua_assert(g->totalbytes == sizeof(LG));  (*g->frealloc)(g->ud, fromstate(L), state_size(LG), 0);

這是lua_close的最後一個步驟。luaC_freeall將釋放所有的 GCObject ,但不包羅有 SFIXEDBIT 的 mainthread 對象 。見 lgc.c 484 行

void luaC_freeall (lua_State *L) bitmask(SFIXEDBIT);  /* mask to collect all elements */  sweepwholelist(L, &g->rootgc);  for (i = 0; i < g->strt.size; i++)  /* free all string lists */    sweepwholelist(L, &g->strt.hash[i]);

這裡 FIXEDBIT 是被疏忽的,而在此以前,FIXEDBIT 被護衛著。見 lstate.c 的 153 行(lua_newstate函數):

g->currentwhite = bit2mask(WHITE0BIT, FIXEDBIT);

這麼做很輕易瞭解,lua 天下的發源,統統根資料都放在這個對象中,要是被提早清理,背面的代碼就會出題目。真實釋放這個對象不是在 GC 中,而是末了那句:

lua_assert(g->totalbytes == sizeof(LG));  (*g->frealloc)(g->ud, fromstate(L), state_size(LG), 0);

順帶還 assert 了一下,最後,世界上是不是只剩下這個結構。

lua_gc 源碼學習二

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.