標籤:
我們曉得,lua 對外的 API 中,統統個 gc 打交道的都經過lua_gc。C 說話構建體系時,普通不講計劃模式。但模式仍是存在的。若要按《計劃模式》中的分類,這應當歸於 Facade 形式。代碼在 lapi.c 的 895 行:
LUA_API int lua_gc (lua_State *L, int what, int data) { int res = 0; global_State *g; lua_lock(L); g = G(L); switch (what) { case LUA_GCSTOP: g->GCthreshold = MAX_LUMEM; break; case LUA_GCRESTART: g->GCthreshold = g->totalbytes; break; case LUA_GCCOLLECT: luaC_fullgc(L); break; case LUA_GCCOUNT: res = cast_int(g->totalbytes >> 10); break; case LUA_GCCOUNTB: res = cast_int(g->totalbytes & 0x3ff); break; case LUA_GCSTEP: { lu_mem a = (cast(lu_mem, data) << 10); if (a <= g->totalbytes) g->GCthreshold = g->totalbytes - a; else g->GCthreshold = 0; while (g->GCthreshold <= g->totalbytes) { luaC_step(L); if (g->gcstate == GCSpause) res = 1; break; } break; } case LUA_GCSETPAUSE: res = g->gcpause; g->gcpause = data; break; case LUA_GCSETSTEPMUL: res = g->gcstepmul; g->gcstepmul = data; break; default: res = -1; } lua_unlock(L); return res; }
從代碼可見,對核心狀態的會見,都是乾脆接見 global state 表的。GC 控制則是調用內部 api 。lua 中對外的 api 和內部模組互動的 api 都是分隔的。這樣井井有條。內部子模組平常名為luaX_xxxX 為子模組代號。對收集器相干的 api 一概以luaC_xxx定名。這些 api 定義在 lgc.h 中。
其間提到的 api 有兩個:
LUAI_FUNC void luaC_step (lua_State *L); LUAI_FUNC void luaC_fullgc (lua_State *L);
用於分步 GC 已經完備 GC 。
另外一個主要的 api 是:
#define luaC_checkGC(L) \ condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1)); \ if (G(L)->totalbytes >= G(L)->GCthreshold) \ luaC_step(L);
它以宏情勢定義進去,用於主動的 GC 。如果我們檢查 lapi.c ldo.c lvm.c ,會發明大部門會導致記憶體增加的 api 中,都挪用了它。包管 gc 可以隨記憶體利用增長而主動停止。
這裡插幾句。
使用自動 gc 會有一個題目。它極可能使體系的峰值記憶體佔用遠超過實際需要量。緣由就在於,收集行動每每產生在調用棧很深之處。當你的利用法式顯現出某種周期性(大大都包驅動的辦事都是這樣)。在一個辦事周期內,常常會援用浩繁姑且對象,這個時辰做 mark 工作,會導致很多且則對象也被 mark 住。
一個閱曆方式是,調用LUA_GCSTOP遏制自動 GC。在周時代按期調用 gcstep 且使用較大的 data 值,在無限個周期做完一整趟 gc 。
另,condhardstacktests 是一個宏,一般為不開啟的。
先來看luaC_fullgc。它用來執行完全的一次 gc 行動。fullgc 並非只是把當前的流程走完。由於以前的 gc 行動可能執行了一半,大概有一些半途加出去的需要收受接管的對象。所以在走完一趟流程後,fullgc 將梗阻著再完好跑一遍 gc 。整個流程有一些最佳化的餘地。即,前半程的 gc 流程並不必嚴厲執行,它其實不需要真的去斷根什麼。只要要把狀態規複。這個工作是怎樣做到的呢?見 lgc.c 的 637 行:
void luaC_fullgc (lua_State *L) { global_State *g = G(L); if (g->gcstate <= GCSpropagate) g->sweepstrgc = 0; g->sweepgc = &g->rootgc; g->gray = NULL; g->grayagain = NULL; g->weak = NULL; g->gcstate = GCSsweepstring; lua_assert(g->gcstate != GCSpause && g->gcstate != GCSpropagate); while (g->gcstate != GCSfinalize) lua_assert(g->gcstate == GCSsweepstring
比力耗時的 mark 步調被簡略跳過了(如果它還沒舉行完的話)。和一般的 mark 流程分歧,平常的 mark 流程末了,會將紅色標識表記標幟反轉。見 lgc.c 548 行,atomic 函數:
g->currentwhite = cast_byte(otherwhite(g));
在 fullgc 的前半程中,間接跳過了 GCSpropagate ,重設了外部狀況,但沒有翻轉白色標誌。這會導致背面的 sweep 流程不會真的開釋那些白色工具。sweep 工作現實做的僅僅把一切工具又從新設定回白色罷了。
接下來便是一個完好不被打斷的 gc 曆程了。
markroot(L); while (g->gcstate != GCSpause) singlestep(L); setthreshold(g);
從根起頭 mark ,直到全部 gc 流程履行終了。最終,從頭配置了 GCthreshold 。註:調用 fullgc 會重設 GCthreshold ,以是如果你曾挪用LUA_GCSTOP停息自動 GC 的話(也是經由過程點竄 GCthreshold 完成),記得再調用一次。
stepgc 要相對於龐大一些。在 lua 手冊的 2.10 詮釋了 garbage-collector pause 和 step multiplier 的意思,卻不給出切確界說。lua_gc的申明裡,也只說“LUA_GCSTEP: 倡議一步增量渣滓收集。步數由 data 控制(越大的值象徵著越多步), 而其詳細寄義(具體數字暗示了幾多)並未尺度化。假如你想控制這個步數,必需嘗試性的測試 data 的值。 要是這一步完成了一個廢物收集周期,返回返回 1 。並無給出精確的寄義。理論中,我們也都因此經曆取值。
回到原始碼,我們就可以搞清晰它們究竟是甚麼了。
case LUA_GCSETPAUSE: res = g->gcpause; g->gcpause = data; break; case LUA_GCSETSTEPMUL: res = g->gcstepmul; g->gcstepmul = data; break;
這裡只是設定 gcpause gcstepmul 。gcpause 實際只在 lgc.c 59 行的 setthreshold 宏頂用到
#define setthreshold(g) (g->GCthreshold = (g->estimate/100) * g->gcpause)
瞥見,GCSETPAUSE 實際上是通過調劑 GCthreshold 來實現的。當 GCthreshold 充足大時,luaC_step不會被luaC_checkGC自動觸發。究竟上,GCSTOP 恰是通過設定一個很大的 GCthreshold 值來實現的。
case LUA_GCSTOP: g->GCthreshold = MAX_LUMEM; break;
gcpause 值的含義很文檔分歧,用來示意和現實記憶體利用量 estimate 的比值(擴大 100 倍)。一旦記憶體使用量量超出這個閥值,就會動身 GC 的工作。
要明白 gcstepmul ,就要從lua_gc的LUA_GCSTEP的實現看起。
case LUA_GCSTEP: { lu_mem a = (cast(lu_mem, data) << 10); if (a <= g->totalbytes) g->GCthreshold = g->totalbytes - a; else g->GCthreshold = 0; while (g->GCthreshold <= g->totalbytes) { luaC_step(L); if (g->gcstate == GCSpause) res = 1; break; } break; }
step 的長度 data 被擴大了 1024 倍。在 lgc.c 的 26 行,也能夠看到
#define GCSTEPSIZE 1024u
咱們權且能夠以為 data 的單元是 KBytes ,和 lua 統共佔用的記憶體 totalbytes 有些干係。
ps. 這裡 totalbytes 是嚴格通過 Alloc 辦理的記憶體量,比來。而後面提到的 estimate 則差別,它是一個預算量,比 totalbytes 要小。這是因為,前方也提到過,userdata 的接納對照特別。被檢測出已經拜候不到的 userdata 佔用的記憶體並不會頓時開釋(擔保 gc 元要領的平安調用),但 estimate 會拋去這部份,不算在實際記憶體使用量量內。
見 lgc.c 544 行
udsize = luaC_separateudata(L, 0);
和 lgc.c 553 行
g->estimate = g->totalbytes - udsize;
從代碼邏輯,咱們臨時可以把 data 瞭解為,必要處置懲罰的位元組數目(以 K bytes 為單元)。若是需求處置的資料量跨越了 totalbytes ,天然就能夠把 GCthreshold 配置為 0 了。
實際上不克不及完整這麼明白。因為 GC 過程並不是一點點回收記憶體,同時可用記憶體愈來愈多。GC 分符號(mark) 肅清(sweep) 調用 userdata 元方法等幾個階段。只要中心的排除階段是真實釋放記憶體的。所以可用記憶體的增加( totalbytes 削減)過程,時候上並不是線性。每每標誌的開消更大。為了讓 gcstep 的每一個程式耗損的時間更光滑,就得有手腕靜態調解 GCthreshold 值。它和 totalbytes 終究影響了每一個 step 的時間。
上面的存眷核心轉向luaC_step ,見 lgc.c 的 611 行:
void luaC_step (lua_State *L) { global_State *g = G(L); l_mem lim = (GCSTEPSIZE/100) * g->gcstepmul; if (lim == 0) lim = (MAX_LUMEM-1)/2; g->gcdept += g->totalbytes - g->GCthreshold; do lim -= singlestep(L); if (g->gcstate == GCSpause) break; while (lim > 0); if (g->gcstate != GCSpause) { if (g->gcdept < GCSTEPSIZE) g->GCthreshold = g->totalbytes + GCSTEPSIZE; else g->gcdept -= GCSTEPSIZE; g->GCthreshold = g->totalbytes; } else lua_assert(g->totalbytes >= g->estimate); setthreshold(g); }
從代碼我們可以看到,GC 的焦點其其實於 singlestep 函數。luaC_step屢次調用幾何次 singlestep 跟 gcstepmul 的值相關。
如果是自動進行的 GC ,當 totalbytes 大於即是 GCthreshold 時,就會觸發luaC_step。每次luaC_step,GCthreshold 城市被調高 1K (GCSTEPSIZE) 直到 GCthreshold 追上 totalbytes 。這個追逐進程凡是產生在 mark 流程。由於這個流程中,totalbytes 是只增不減的。
如果是手控 GC ,屢次 gcstep 調用履行幾許次luaC_step則跟 data 值相關。大致上是 1 就默示一次(在 mark 過程當中便是如許)到了 sweep 流程就未必了。這和 singlestep 調用次數,即 gcstepmul 的值有關。它影響了 totalbytes 的減小速度。
以是,一兩句話很難嚴厲定義出這些控制 GC 步進量的參數的含義,只能漸漸瀏覽代碼,看看實現了。
在 lua 手冊的 2.10 如許描寫“step multiplier 節制了收集器相對於記憶體指派的速率。更大的數字將致使收集器事情的更自動的同時,也使每步搜集的尺寸增添。 小於 1 的值會使收集器勞動的很是慢,大概導致搜集器永久都竣事不了以後周期。 預設值為 2 ,這象徵著收集器將之記憶體指派器的兩倍速運轉。”
從代碼看,這絕非嚴酷界說。最少從本日已闡發的代碼中還看不出這一點。
lua_gc 源碼學習三