Simpler control for major collections

This commit is contained in:
Roberto Ierusalimschy
2022-11-29 10:37:08 -03:00
parent 152b51955a
commit d324a0ccf9
6 changed files with 76 additions and 135 deletions

4
lapi.c
View File

@@ -1199,7 +1199,7 @@ LUA_API int lua_gc (lua_State *L, int what, ...) {
case LUA_GCGEN: { case LUA_GCGEN: {
int minormul = va_arg(argp, int); int minormul = va_arg(argp, int);
int majormul = va_arg(argp, int); int majormul = va_arg(argp, int);
res = isdecGCmodegen(g) ? LUA_GCGEN : LUA_GCINC; res = (g->gckind == KGC_INC) ? LUA_GCINC : LUA_GCGEN;
if (minormul != 0) if (minormul != 0)
g->genminormul = minormul; g->genminormul = minormul;
if (majormul != 0) if (majormul != 0)
@@ -1211,7 +1211,7 @@ LUA_API int lua_gc (lua_State *L, int what, ...) {
int pause = va_arg(argp, int); int pause = va_arg(argp, int);
int stepmul = va_arg(argp, int); int stepmul = va_arg(argp, int);
int stepsize = va_arg(argp, int); int stepsize = va_arg(argp, int);
res = isdecGCmodegen(g) ? LUA_GCGEN : LUA_GCINC; res = (g->gckind == KGC_INC) ? LUA_GCINC : LUA_GCGEN;
if (pause != 0) if (pause != 0)
setgcparam(g->gcpause, pause); setgcparam(g->gcpause, pause);
if (stepmul != 0) if (stepmul != 0)

188
lgc.c
View File

@@ -28,6 +28,13 @@
#include "ltm.h" #include "ltm.h"
/*
** Number of fixed (luaC_fix) objects in a Lua state: metafield names,
** plus reserved words, plus "_ENV", plus the memory-error message.
*/
#define NFIXED (TM_N + NUM_RESERVED + 2)
/* /*
** Maximum number of elements to sweep in each single step. ** Maximum number of elements to sweep in each single step.
** (Large enough to dissipate fixed overheads but small enough ** (Large enough to dissipate fixed overheads but small enough
@@ -35,18 +42,6 @@
*/ */
#define GCSWEEPMAX 20 #define GCSWEEPMAX 20
/*
** Maximum number of finalizers to call in each single step.
*/
#define GCFINMAX 10
/*
** Cost of calling one finalizer.
*/
#define GCFINALIZECOST 50
/* mask with all color bits */ /* mask with all color bits */
#define maskcolors (bitmask(BLACKBIT) | WHITEBITS) #define maskcolors (bitmask(BLACKBIT) | WHITEBITS)
@@ -205,7 +200,7 @@ void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v) {
} }
else { /* sweep phase */ else { /* sweep phase */
lua_assert(issweepphase(g)); lua_assert(issweepphase(g));
if (g->gckind == KGC_INC) /* incremental mode? */ if (g->gckind != KGC_GEN) /* incremental mode? */
makewhite(g, o); /* mark 'o' as white to avoid other barriers */ makewhite(g, o); /* mark 'o' as white to avoid other barriers */
} }
} }
@@ -398,7 +393,7 @@ static void cleargraylists (global_State *g) {
*/ */
static void restartcollection (global_State *g) { static void restartcollection (global_State *g) {
cleargraylists(g); cleargraylists(g);
g->marked = TM_N + NUM_RESERVED + 2; g->marked = NFIXED;
markobject(g, g->mainthread); markobject(g, g->mainthread);
markvalue(g, &g->l_registry); markvalue(g, &g->l_registry);
markmt(g); markmt(g);
@@ -926,17 +921,6 @@ static void GCTM (lua_State *L) {
} }
/*
** Call a few finalizers
*/
static void runafewfinalizers (lua_State *L, int n) {
global_State *g = G(L);
int i;
for (i = 0; i < n && g->tobefnz; i++)
GCTM(L); /* call one finalizer */
}
/* /*
** call all pending finalizers ** call all pending finalizers
*/ */
@@ -1295,8 +1279,7 @@ static void atomic2gen (lua_State *L, global_State *g) {
sweep2old(L, &g->tobefnz); sweep2old(L, &g->tobefnz);
g->gckind = KGC_GEN; g->gckind = KGC_GEN;
g->lastatomic = 0; g->GClastmajor = gettotalobjs(g); /* base for memory control */
g->GCestimate = gettotalobjs(g); /* base for memory control */
finishgencycle(L, g); finishgencycle(L, g);
} }
@@ -1306,7 +1289,7 @@ static void atomic2gen (lua_State *L, global_State *g) {
** total number of objects grows 'genminormul'%. ** total number of objects grows 'genminormul'%.
*/ */
static void setminordebt (global_State *g) { static void setminordebt (global_State *g) {
luaE_setdebt(g, -(cast(l_obj, (gettotalobjs(g) / 100)) * g->genminormul)); luaE_setdebt(g, -(gettotalobjs(g) / 100) * g->genminormul);
} }
@@ -1338,7 +1321,6 @@ static void enterinc (global_State *g) {
g->finobjrold = g->finobjold1 = g->finobjsur = NULL; g->finobjrold = g->finobjold1 = g->finobjsur = NULL;
g->gcstate = GCSpause; g->gcstate = GCSpause;
g->gckind = KGC_INC; g->gckind = KGC_INC;
g->lastatomic = 0;
} }
@@ -1347,13 +1329,18 @@ static void enterinc (global_State *g) {
*/ */
void luaC_changemode (lua_State *L, int newmode) { void luaC_changemode (lua_State *L, int newmode) {
global_State *g = G(L); global_State *g = G(L);
if (newmode != g->gckind) { if (newmode != g->gckind) { /* does it need to change? */
if (newmode == KGC_GEN) /* entering generational mode? */ if (newmode == KGC_INC) { /* entering incremental mode? */
if (g->gckind == KGC_GENMAJOR)
g->gckind = KGC_INC; /* already incremental but in name */
else
enterinc(g); /* entering incremental mode */
}
else {
lua_assert(newmode == KGC_GEN);
entergen(L, g); entergen(L, g);
else }
enterinc(g); /* entering incremental mode */
} }
g->lastatomic = 0;
} }
@@ -1367,93 +1354,52 @@ static void fullgen (lua_State *L, global_State *g) {
/* /*
** Does a major collection after last collection was a "bad collection". ** Does a major collector up to the atomic phase and then either
** ** returns to minor collections or stays doing major ones. If the
** When the program is building a big structure, it allocates lots of ** number of objects collected this time (numobjs - marked) is more than
** memory but generates very little garbage. In those scenarios, ** half the number of objects created since the last major collection
** the generational mode just wastes time doing small collections, and ** (numobjs - lastmajor), it goes back to minor collections.
** major collections are frequently what we call a "bad collection", a
** collection that frees too few objects. To avoid the cost of switching
** between generational mode and the incremental mode needed for full
** (major) collections, the collector tries to stay in incremental mode
** after a bad collection, and to switch back to generational mode only
** after a "good" collection (one that traverses less than 9/8 objects
** of the previous one).
** The collector must choose whether to stay in incremental mode or to
** switch back to generational mode before sweeping. At this point, it
** does not know the real memory in use, so it cannot use memory to
** decide whether to return to generational mode. Instead, it uses the
** number of objects traversed (returned by 'atomic') as a proxy. The
** field 'g->lastatomic' keeps this count from the last collection.
** ('g->lastatomic != 0' also means that the last collection was bad.)
*/ */
static void stepgenfull (lua_State *L, global_State *g) { static void genmajorstep (lua_State *L, global_State *g) {
lu_mem lastatomic = g->lastatomic; /* count from last collection */ l_obj lastmajor = g->GClastmajor; /* count from last collection */
if (g->gckind == KGC_GEN) /* still in generational mode? */ l_obj numobjs = gettotalobjs(g); /* current count */
enterinc(g); /* enter incremental mode */
luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */ luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */
g->marked = 0;
atomic(L); /* mark everybody */ atomic(L); /* mark everybody */
if (g->marked < lastatomic + (lastatomic >> 3)) { /* good collection? */ if ((numobjs - g->marked) > ((numobjs - lastmajor) >> 1)) {
atomic2gen(L, g); /* return to generational mode */ atomic2gen(L, g); /* return to generational mode */
setminordebt(g); setminordebt(g);
} }
else { /* another bad collection; stay in incremental mode */ else { /* bad collection; stay in major mode */
g->GCestimate = gettotalobjs(g); /* first estimate */;
g->lastatomic = g->marked;
entersweep(L); entersweep(L);
luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */ luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */
setpause(g); setpause(g);
g->GClastmajor = gettotalobjs(g);
} }
} }
/* /*
** Does a generational "step". ** Does a generational "step". If the total number of objects grew
** Usually, this means doing a minor collection and setting the debt to ** more than 'majormul'% since the last major collection, does a
** make another collection when memory grows 'genminormul'% larger. ** major collection. Otherwise, does a minor collection. The test
** ** ('GCdebt' > 0) avoids major collections when the step originated from
** However, there are exceptions. If memory grows 'genmajormul'% ** 'collectgarbage("step")'.
** larger than it was at the end of the last major collection (kept
** in 'g->GCestimate'), the function does a major collection. At the
** end, it checks whether the major collection was able to free a
** decent amount of memory (at least half the growth in memory since
** previous major collection). If so, the collector keeps its state,
** and the next collection will probably be minor again. Otherwise,
** we have what we call a "bad collection". In that case, set the field
** 'g->lastatomic' to signal that fact, so that the next collection will
** go to 'stepgenfull'.
**
** 'GCdebt <= 0' means an explicit call to GC step with "size" zero;
** in that case, do a minor collection.
*/ */
static void genstep (lua_State *L, global_State *g) { static void genstep (lua_State *L, global_State *g) {
if (g->lastatomic != 0) /* last collection was a bad one? */ l_obj majorbase = g->GClastmajor; /* count after last major collection */
stepgenfull(L, g); /* do a full step */ l_obj majorinc = (majorbase / 100) * getgcparam(g->genmajormul);
else { if (g->GCdebt > 0 && gettotalobjs(g) > majorbase + majorinc) {
l_obj majorbase = g->GCestimate; /* objects after last major collection */ /* do a major collection */
l_obj majorinc = (majorbase / 100) * getgcparam(g->genmajormul); enterinc(g);
if (g->GCdebt > 0 && gettotalobjs(g) > majorbase + majorinc) { g->gckind = KGC_GENMAJOR;
g->marked = 0; genmajorstep(L, g);
fullgen(L, g); /* do a major collection */ }
if (gettotalobjs(g) < majorbase + (majorinc / 2)) { else { /* regular case; do a minor collection */
/* collected at least half of object growth since last major g->marked = 0;
collection; keep doing minor collections. */ youngcollection(L, g);
lua_assert(g->lastatomic == 0); setminordebt(g);
} lua_assert(g->GClastmajor == majorbase);
else { /* bad collection */
g->lastatomic = g->marked; /* signal that last collection was bad */
setpause(g); /* do a long wait for next (major) collection */
}
}
else { /* regular case; do a minor collection */
g->marked = 0;
youngcollection(L, g);
setminordebt(g);
g->GCestimate = majorbase; /* preserve base value */
}
} }
lua_assert(isdecGCmodegen(g));
} }
/* }====================================================== */ /* }====================================================== */
@@ -1557,11 +1503,8 @@ static l_obj atomic (lua_State *L) {
static void sweepstep (lua_State *L, global_State *g, static void sweepstep (lua_State *L, global_State *g,
int nextstate, GCObject **nextlist) { int nextstate, GCObject **nextlist) {
if (g->sweepgc) { if (g->sweepgc)
l_obj olddebt = g->GCdebt;
g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX); g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX);
g->GCestimate += g->GCdebt - olddebt; /* update estimate */
}
else { /* enter next state */ else { /* enter next state */
g->gcstate = nextstate; g->gcstate = nextstate;
g->sweepgc = nextlist; g->sweepgc = nextlist;
@@ -1618,11 +1561,11 @@ static l_obj singlestep (lua_State *L) {
work = 0; work = 0;
break; break;
} }
case GCScallfin: { /* call remaining finalizers */ case GCScallfin: { /* call finalizers */
if (g->tobefnz && !g->gcemergency) { if (g->tobefnz && !g->gcemergency) {
g->gcstopem = 0; /* ok collections during finalizers */ g->gcstopem = 0; /* ok collections during finalizers */
runafewfinalizers(L, GCFINMAX); GCTM(L); /* call one finalizer */
work = GCFINMAX * GCFINALIZECOST; work = 1;
} }
else { /* emergency mode or no more finalizers */ else { /* emergency mode or no more finalizers */
g->gcstate = GCSpause; /* finish collection */ g->gcstate = GCSpause; /* finish collection */
@@ -1679,10 +1622,17 @@ void luaC_step (lua_State *L) {
global_State *g = G(L); global_State *g = G(L);
lua_assert(!g->gcemergency); lua_assert(!g->gcemergency);
if (gcrunning(g)) { /* running? */ if (gcrunning(g)) { /* running? */
if(isdecGCmodegen(g)) switch (g->gckind) {
genstep(L, g); case KGC_INC:
else incstep(L, g);
incstep(L, g); break;
case KGC_GEN:
genstep(L, g);
break;
case KGC_GENMAJOR:
genmajorstep(L, g);
break;
}
} }
} }
@@ -1700,7 +1650,7 @@ static void fullinc (lua_State *L, global_State *g) {
/* finish any pending sweep phase to start a new cycle */ /* finish any pending sweep phase to start a new cycle */
luaC_runtilstate(L, bitmask(GCSpause)); luaC_runtilstate(L, bitmask(GCSpause));
luaC_runtilstate(L, bitmask(GCScallfin)); /* run up to finalizers */ luaC_runtilstate(L, bitmask(GCScallfin)); /* run up to finalizers */
/* estimate must be correct after a full GC cycle */ /* 'marked' must be correct after a full GC cycle */
lua_assert(g->marked == gettotalobjs(g)); lua_assert(g->marked == gettotalobjs(g));
luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */ luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */
setpause(g); setpause(g);
@@ -1716,10 +1666,10 @@ void luaC_fullgc (lua_State *L, int isemergency) {
global_State *g = G(L); global_State *g = G(L);
lua_assert(!g->gcemergency); lua_assert(!g->gcemergency);
g->gcemergency = isemergency; /* set flag */ g->gcemergency = isemergency; /* set flag */
if (g->gckind == KGC_INC) if (g->gckind == KGC_GEN)
fullinc(L, g);
else
fullgen(L, g); fullgen(L, g);
else
fullinc(L, g);
g->gcemergency = 0; g->gcemergency = 0;
} }

8
lgc.h
View File

@@ -141,14 +141,6 @@
#define LUAI_GCSTEPSIZE 8 /* 256 objects */ #define LUAI_GCSTEPSIZE 8 /* 256 objects */
/*
** Check whether the declared GC mode is generational. While in
** generational mode, the collector can go temporarily to incremental
** mode to improve performance. This is signaled by 'g->lastatomic != 0'.
*/
#define isdecGCmodegen(g) (g->gckind == KGC_GEN || g->lastatomic != 0)
/* /*
** Control when GC is running: ** Control when GC is running:
*/ */

View File

@@ -391,7 +391,6 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
g->totalobjs = 1; g->totalobjs = 1;
g->marked = 0; g->marked = 0;
g->GCdebt = 0; g->GCdebt = 0;
g->lastatomic = 0;
setivalue(&g->nilvalue, 0); /* to signal that state is not yet built */ setivalue(&g->nilvalue, 0); /* to signal that state is not yet built */
setgcparam(g->gcpause, LUAI_GCPAUSE); setgcparam(g->gcpause, LUAI_GCPAUSE);
setgcparam(g->gcstepmul, LUAI_GCMUL); setgcparam(g->gcstepmul, LUAI_GCMUL);

View File

@@ -145,12 +145,13 @@ struct lua_longjmp; /* defined in ldo.c */
/* kinds of Garbage Collection */ /* kinds of Garbage Collection */
#define KGC_INC 0 /* incremental gc */ #define KGC_INC 0 /* incremental gc */
#define KGC_GEN 1 /* generational gc */ #define KGC_GEN 1 /* generational gc */
#define KGC_GENMAJOR 2 /* generational in "major" mode */
typedef struct stringtable { typedef struct stringtable {
TString **hash; TString **hash; /* array of buckets (linked lists of strings) */
int nuse; /* number of elements */ int nuse; /* number of elements */
int size; int size; /* number of buckets */
} stringtable; } stringtable;
@@ -253,8 +254,7 @@ typedef struct global_State {
l_obj totalobjs; /* total number of objects allocated - GCdebt */ l_obj totalobjs; /* total number of objects allocated - GCdebt */
l_obj GCdebt; /* bytes allocated not yet compensated by the collector */ l_obj GCdebt; /* bytes allocated not yet compensated by the collector */
l_obj marked; /* number of objects marked in a GC cycle */ l_obj marked; /* number of objects marked in a GC cycle */
lu_mem GCestimate; /* an estimate of the non-garbage memory in use */ l_obj GClastmajor; /* objects at last major collection */
lu_mem lastatomic; /* see function 'genstep' in file 'lgc.c' */
stringtable strt; /* hash table for strings */ stringtable strt; /* hash table for strings */
TValue l_registry; TValue l_registry;
TValue nilvalue; /* a nil value */ TValue nilvalue; /* a nil value */

View File

@@ -297,7 +297,7 @@ static int testobjref1 (global_State *g, GCObject *f, GCObject *t) {
if (isdead(g,t)) return 0; if (isdead(g,t)) return 0;
if (issweepphase(g)) if (issweepphase(g))
return 1; /* no invariants */ return 1; /* no invariants */
else if (g->gckind == KGC_INC) else if (g->gckind != KGC_GEN)
return !(isblack(f) && iswhite(t)); /* basic incremental invariant */ return !(isblack(f) && iswhite(t)); /* basic incremental invariant */
else { /* generational mode */ else { /* generational mode */
if ((getage(f) == G_OLD && isblack(f)) && !isold(t)) if ((getage(f) == G_OLD && isblack(f)) && !isold(t))