lua_gc 源碼學習三

來源:互聯網
上載者:User

標籤:

我們曉得,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 源碼學習三

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.