接上篇, 在真正阅读luaC_step源码之前,先来看一下Lua的垃圾回收器对外提供了哪些可调参数, 以及这些参数是如何控制垃圾回收器的.
Lua提供了一个函数collectgarbage([opt[,arg]])用于控制GC的一些形为.
collectgarbage通过第一个参数opt来执行不同的功能:
“collect”: 执行一次完整的垃圾回收循环,这是默认选项.
“stop”: 停止垃圾回收器的运行, 在调用”restart”前,回收器只有被显式调用时才运行.
“restart”: 重新开始垃圾回收器的自动运行.
“count”: 以Kbytes为单位显示Lua使用的内存.
“isrunning”: 表示垃圾回收器是否在工作.
“step”: 单步运行垃圾回收器, 步长”大小”由arg控制.
“setpause”: 将arg设置为回收器的’间歇率’.
“setstepmul”: 将arg设置为回收器的’步进倍率’.
“collect/stop/restart/count/isrunning” 都是很直观, 重点应该是”step/setpause/setstepmul”是如何影响luaC_step的执行流程的.
先来看一下”step/setpause/setstepmul”都做了哪些操作.
//lapi.c case LUA_GCCOUNT: { /* GC values are expressed in Kbytes: #bytes/2^10 */ res = cast_int(gettotalbytes(g) >> 10); break; } case LUA_GCSTEP: { //"step" l_mem debt = 1; /* =1 to signal that it did an actual step */ lu_byte oldrunning = g->gcrunning; g->gcrunning = 1; /* allow GC to run */ if (data == 0) { luaE_setdebt(g, -GCSTEPSIZE); /* to do a "small" step */ luaC_step(L); } else { /* add 'data' to total debt */ debt = cast(l_mem, data) * 1024 + g->GCdebt; luaE_setdebt(g, debt); luaC_checkGC(L); } g->gcrunning = oldrunning; /* restore previous state */ if (debt > 0 && g->gcstate == GCSpause) /* end of cycle? */ res = 1; /* signal it */ break; } case LUA_GCSETPAUSE: { //"setpause" res = g->gcpause; g->gcpause = data; break; } case LUA_GCSETSTEPMUL: { //"setstepmul" res = g->gcstepmul; if (data < 40) data = 40; /* avoid ridiculous low values (and 0) */ g->gcstepmul = data; break; } //lstate.h /* actual number of total bytes allocated */ #define gettotalbytes(g) cast(lu_mem, (g)->totalbytes + (g)->GCdebt) typedef struct global_State { ... l_mem totalbytes; /* number of bytes currently allocated - GCdebt */ l_mem GCdebt; /* bytes allocated not yet compensated by the collector */ lu_mem GCestimate; /* an estimate of the non-garbage memory in use */ int gcpause; /* size of pause between successive GCs */ int gcstepmul; /* GC 'granularity' */ ... }; //lstate.c #if !defined(LUAI_GCPAUSE) #define LUAI_GCPAUSE 200 /* 200% */ #endif #if !defined(LUAI_GCMUL) #define LUAI_GCMUL 200 /* GC runs 'twice the speed' of memory allocation */ #endif LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { int i; lua_State *L; global_State *g; LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG))); if (l == NULL) return NULL; L = &l->l.l; g = &l->g; ... g->gcrunning = 0; /* no GC while building state */ g->GCestimate = 0; g->totalbytes = sizeof(LG); g->GCdebt = 0; g->gcpause = LUAI_GCPAUSE; g->gcstepmul = LUAI_GCMUL; ... } void luaE_setdebt (global_State *g, l_mem debt) { l_mem tb = gettotalbytes(g); lua_assert(tb > 0); if (debt < tb - MAX_LMEM) debt = tb - MAX_LMEM; /* will make 'totalbytes == MAX_LMEM' */ g->totalbytes = tb - debt; g->GCdebt = debt; }
从上面代码可以得出两点结论:
1. global_State.totalbytes + global_State.GCdebt 就是整个LuaVM使用的全部内存总量
2. luaE_setdebt函数只会修改global_State.GCdebt, 但是不会改变整个LuaVM的使用内存总量
3. 步进倍率是靠变量global_State.gcstepmul控制的
4. 间歇率是靠变量global_State.gcpause来控制的
5. 根据注释可知GCestimate代表在使用的非垃圾内存
下面代码的就是GC如何通过global_State.gcstepmul和global_State.gcpause来精巧的控制步进倍率和间歇率
//lgc.c static l_mem getdebt (global_State *g) { l_mem debt = g->GCdebt; int stepmul = g->gcstepmul; if (debt <= 0) return 0; /* minimal debt */ else { debt = (debt / STEPMULADJ) + 1; debt = (debt < MAX_LMEM / stepmul) ? debt * stepmul : MAX_LMEM; return debt; } } static void setpause (global_State *g) { l_mem threshold, debt; l_mem estimate = g->GCestimate / PAUSEADJ; /* adjust 'estimate' */ lua_assert(estimate > 0); threshold = (g->gcpause < MAX_LMEM / estimate) /* overflow? */ ? estimate * g->gcpause /* no overflow */ : MAX_LMEM; /* overflow; truncate to maximum */ debt = gettotalbytes(g) - threshold; luaE_setdebt(g, debt); } void luaC_step (lua_State *L) { global_State *g = G(L); l_mem debt = getdebt(g); /* GC deficit (be paid now) */ if (!g->gcrunning) { /* not running? */ luaE_setdebt(g, -GCSTEPSIZE * 10); /* avoid being called too often */ return; } do { /* repeat until pause or enough "credit" (negative debt) */ lu_mem work = singlestep(L); /* perform one single step */ debt -= work; } while (debt > -GCSTEPSIZE && g->gcstate != GCSpause); if (g->gcstate == GCSpause) setpause(g); /* pause until next cycle */ else { debt = (debt / g->gcstepmul) * STEPMULADJ; /* convert 'work units' to Kb */ luaE_setdebt(g, debt); runafewfinalizers(L); } }
回忆一下上篇内容:
1. global_State.GCdebt 代表着内存分配所产生的债务
2. checkGC函数保证只有global_State.GCdebt > 0 才会执行luaC_step.
getdebt会将当前债务(global_State.GCdebt)根据变量global_State.gcstepmul进行放大
luaC_step中的while循环会保证只有getdebt返回的债务值全部被偿还完之后才会退出(或者执行完了整个GC流程)
这样即可通过设置更高的global_State.gcstepmul值来达到增大步长
再来看setpause函数, 在不考虑边界情况下,下面代码可以看的更直观一些:
l_mem threshold = g->gcpause * g->GCestimate / PAUSEADJ; luaE_setdebt(g, gettotalbytes(g) - threshold);
当gcpause=2*PAUSEADJ时, threshold = 2 * g->GCestimate.
在luaC_fullgc代码中可以确认当GC循环执行到GCScallfin状态以前,g->GCestimate与gettotalbytes(g)必然相等.
暂时不考虑GCScallfin状态对内存的影响,setpuase函数会将g->GCdebt设置为负的g->GCestimate, 这将会导致,当内存分配超过g->GCestimate之后才会再次开始垃圾回收循环, 与g->gcpause的含义一致.
void luaC_fullgc (lua_State *L, int isemergency) {
…
luaC_runtilstate(L, bitmask(GCScallfin)); /* run up to finalizers */
/* estimate must be correct after a full GC cycle */
lua_assert(g->GCestimate == gettotalbytes(g));
luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */
g->gckind = KGC_NORMAL;
setpause(g);
}
下篇接着看singlestep是如何执行标记清除的.