From 666e95a66d1a2ceb98bdf320980b3f655264a9c9 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 20 Dec 2023 11:06:27 -0300 Subject: [PATCH] Option 0 for step multiplier makes GC non-incremental --- lgc.c | 67 +++++++++++++++++++++++++++++++----------------- lgc.h | 12 +++++---- llimits.h | 8 ++++-- ltests.c | 8 +++--- manual/manual.of | 28 ++++++++++---------- testes/gengc.lua | 11 ++++++-- 6 files changed, 84 insertions(+), 50 deletions(-) diff --git a/lgc.c b/lgc.c index ecd142cb..114b32d3 100644 --- a/lgc.c +++ b/lgc.c @@ -93,6 +93,7 @@ */ #define markobjectN(g,t) { if (t) markobject(g,t); } + static void reallymarkobject (global_State *g, GCObject *o); static l_obj atomic (lua_State *L); static void entersweep (lua_State *L); @@ -831,10 +832,10 @@ static void freeobj (lua_State *L, GCObject *o) { ** for next collection cycle. Return where to continue the traversal or ** NULL if list is finished. */ -static GCObject **sweeplist (lua_State *L, GCObject **p, int countin) { +static GCObject **sweeplist (lua_State *L, GCObject **p, l_obj countin) { global_State *g = G(L); int ow = otherwhite(g); - int i; + l_obj i; int white = luaC_white(g); /* current white */ for (i = 0; *p != NULL && i < countin; i++) { GCObject *curr = *p; @@ -1357,8 +1358,8 @@ static void setminordebt (global_State *g) { ** collection. */ static void entergen (lua_State *L, global_State *g) { - luaC_runtilstate(L, bitmask(GCSpause)); /* prepare to start a new cycle */ - luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */ + luaC_runtilstate(L, GCSpause, 1); /* prepare to start a new cycle */ + luaC_runtilstate(L, GCSpropagate, 1); /* start new cycle */ atomic(L); /* propagates all and then do the atomic stuff */ atomic2gen(L, g); setminordebt(g); /* set debt assuming next cycle will be minor */ @@ -1515,10 +1516,14 @@ static l_obj atomic (lua_State *L) { } +/* +** Do a sweep step. The normal case (not fast) sweeps at most GCSWEEPMAX +** elements. The fast case sweeps the whole list. +*/ static void sweepstep (lua_State *L, global_State *g, - int nextstate, GCObject **nextlist) { + int nextstate, GCObject **nextlist, int fast) { if (g->sweepgc) - g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX); + g->sweepgc = sweeplist(L, g->sweepgc, fast ? MAX_LOBJ : GCSWEEPMAX); else { /* enter next state */ g->gcstate = nextstate; g->sweepgc = nextlist; @@ -1526,7 +1531,19 @@ static void sweepstep (lua_State *L, global_State *g, } -static l_obj singlestep (lua_State *L) { +/* +** Performs one incremental "step" in an incremental garbage collection. +** For indivisible work, a step goes to the next state. When marking +** (propagating), a step traverses one object. When sweeping, a step +** sweeps GCSWEEPMAX objects, to avoid a big overhead for sweeping +** objects one by one. (Sweeping is inexpensive, no matter the +** object.) When 'fast' is true, 'singlestep' tries to finish a state +** "as fast as possible". In particular, it skips the propagation +** phase and leaves all objects to be traversed by the atomic phase: +** That avoids traversing twice some objects, such as theads and +** weak tables. +*/ +static l_obj singlestep (lua_State *L, int fast) { global_State *g = G(L); l_obj work; lua_assert(!g->gcstopem); /* collector is not reentrant */ @@ -1539,7 +1556,7 @@ static l_obj singlestep (lua_State *L) { break; } case GCSpropagate: { - if (g->gray == NULL) { /* no more gray objects? */ + if (fast || g->gray == NULL) { g->gcstate = GCSenteratomic; /* finish propagate phase */ work = 0; } @@ -1556,17 +1573,17 @@ static l_obj singlestep (lua_State *L) { break; } case GCSswpallgc: { /* sweep "regular" objects */ - sweepstep(L, g, GCSswpfinobj, &g->finobj); + sweepstep(L, g, GCSswpfinobj, &g->finobj, fast); work = GCSWEEPMAX; break; } case GCSswpfinobj: { /* sweep objects with finalizers */ - sweepstep(L, g, GCSswptobefnz, &g->tobefnz); + sweepstep(L, g, GCSswptobefnz, &g->tobefnz, fast); work = GCSWEEPMAX; break; } case GCSswptobefnz: { /* sweep objects to be finalized */ - sweepstep(L, g, GCSswpend, NULL); + sweepstep(L, g, GCSswpend, NULL, fast); work = GCSWEEPMAX; break; } @@ -1596,14 +1613,15 @@ static l_obj singlestep (lua_State *L) { /* -** advances the garbage collector until it reaches a state allowed -** by 'statemask' +** Advances the garbage collector until it reaches the given state. +** (The option 'fast' is only for testing; in normal code, 'fast' +** here is always true.) */ -void luaC_runtilstate (lua_State *L, int statesmask) { +void luaC_runtilstate (lua_State *L, int state, int fast) { global_State *g = G(L); lua_assert(g->gckind == KGC_INC); - while (!testbit(statesmask, g->gcstate)) - singlestep(L); + while (state != g->gcstate) + singlestep(L, fast); } @@ -1618,8 +1636,13 @@ void luaC_runtilstate (lua_State *L, int statesmask) { static void incstep (lua_State *L, global_State *g) { l_obj stepsize = cast(l_obj, 1) << g->gcstepsize; l_obj work2do = applygcparam(g, gcstepmul, stepsize); - do { /* repeat until pause or enough "credit" (negative debt) */ - l_obj work = singlestep(L); /* perform one single step */ + int fast = 0; + if (work2do == 0) { /* special case: do a full collection */ + work2do = MAX_LOBJ; /* do unlimited work */ + fast = 1; + } + do { /* repeat until pause or enough work */ + l_obj work = singlestep(L, fast); /* perform one single step */ if (g->gckind == KGC_GENMINOR) /* returned to minor collections? */ return; /* nothing else to be done here */ work2do -= work; @@ -1665,13 +1688,11 @@ static void fullinc (lua_State *L, global_State *g) { if (keepinvariant(g)) /* black objects? */ entersweep(L); /* sweep everything to turn them back to white */ /* finish any pending sweep phase to start a new cycle */ - luaC_runtilstate(L, bitmask(GCSpause)); - luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */ - g->gcstate = GCSenteratomic; /* go straight to atomic phase ??? */ - luaC_runtilstate(L, bitmask(GCScallfin)); /* run up to finalizers */ + luaC_runtilstate(L, GCSpause, 1); + luaC_runtilstate(L, GCScallfin, 1); /* run up to finalizers */ /* 'marked' must be correct after a full GC cycle */ lua_assert(g->marked == gettotalobjs(g)); - luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */ + luaC_runtilstate(L, GCSpause, 1); /* finish collection */ setpause(g); } diff --git a/lgc.h b/lgc.h index 4cbc6e61..e8dee8a0 100644 --- a/lgc.h +++ b/lgc.h @@ -182,12 +182,14 @@ /* incremental */ -/* wait memory to double before starting new cycle */ -#define LUAI_GCPAUSE 200 +/* Number of objects must be LUAI_GCPAUSE% before starting new cycle */ +#define LUAI_GCPAUSE 300 -#define LUAI_GCMUL 300 /* step multiplier */ +/* Step multiplier. (Roughly, the collector handles LUAI_GCMUL% objects + for each new allocated object.) */ +#define LUAI_GCMUL 200 -/* how many objects to allocate before next GC step (log2) */ +/* How many objects to allocate before next GC step (log2) */ #define LUAI_GCSTEPSIZE 8 /* 256 objects */ @@ -244,7 +246,7 @@ LUAI_FUNC void luaC_fix (lua_State *L, GCObject *o); LUAI_FUNC void luaC_freeallobjects (lua_State *L); LUAI_FUNC void luaC_step (lua_State *L); -LUAI_FUNC void luaC_runtilstate (lua_State *L, int statesmask); +LUAI_FUNC void luaC_runtilstate (lua_State *L, int state, int fast); LUAI_FUNC void luaC_fullgc (lua_State *L, int isemergency); LUAI_FUNC GCObject *luaC_newobj (lua_State *L, int tt, size_t sz); LUAI_FUNC GCObject *luaC_newobjdt (lua_State *L, int tt, size_t sz, diff --git a/llimits.h b/llimits.h index 47dee9a6..3bcd1b7f 100644 --- a/llimits.h +++ b/llimits.h @@ -33,7 +33,8 @@ typedef unsigned long lu_mem; typedef long l_obj; #endif /* } */ -#define MAX_LOBJ cast(l_obj, ~cast(lu_mem, 0) >> 1) +#define MAX_LOBJ \ + cast(l_obj, (cast(lu_mem, 1) << (sizeof(l_obj) * CHAR_BIT - 1)) - 1) /* chars used as small naturals (so that 'char' is reserved for characters) */ @@ -44,7 +45,10 @@ typedef signed char ls_byte; /* maximum value for size_t */ #define MAX_SIZET ((size_t)(~(size_t)0)) -/* maximum size visible for Lua (must be representable in a lua_Integer) */ +/* +** Maximum size for strings and userdata visible for Lua (should be +** representable in a lua_Integer) +*/ #define MAX_SIZE (sizeof(size_t) < sizeof(lua_Integer) ? MAX_SIZET \ : (size_t)(LUA_MAXINTEGER)) diff --git a/ltests.c b/ltests.c index 9bc2f0da..51e4ff9b 100644 --- a/ltests.c +++ b/ltests.c @@ -941,10 +941,10 @@ static int gc_printobj (lua_State *L) { static int gc_state (lua_State *L) { static const char *statenames[] = { - "propagate", "atomic", "enteratomic", "sweepallgc", "sweepfinobj", + "propagate", "atomic", "sweepallgc", "sweepfinobj", "sweeptobefnz", "sweepend", "callfin", "pause", ""}; static const int states[] = { - GCSpropagate, GCSenteratomic, GCSatomic, GCSswpallgc, GCSswpfinobj, + GCSpropagate, GCSenteratomic, GCSswpallgc, GCSswpfinobj, GCSswptobefnz, GCSswpend, GCScallfin, GCSpause, -1}; int option = states[luaL_checkoption(L, 1, "", statenames)]; if (option == -1) { @@ -957,9 +957,9 @@ static int gc_state (lua_State *L) { luaL_error(L, "cannot change states in generational mode"); lua_lock(L); if (option < g->gcstate) { /* must cross 'pause'? */ - luaC_runtilstate(L, bitmask(GCSpause)); /* run until pause */ + luaC_runtilstate(L, GCSpause, 1); /* run until pause */ } - luaC_runtilstate(L, bitmask(option)); + luaC_runtilstate(L, option, 0); /* do not skip propagation state */ lua_assert(G(L)->gcstate == option); lua_unlock(L); return 0; diff --git a/manual/manual.of b/manual/manual.of index ef1bdfd2..e6a3cd9e 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -664,7 +664,7 @@ Values equal to or less than 100 mean the collector will not wait to start a new cycle. A value of 200 means that the collector waits for the total number of objects to double before starting a new cycle. -The default value is 200; the maximum value is 1000. +The default value is 300; the maximum value is 1000. The garbage-collector step multiplier controls the speed of the collector relative to @@ -674,7 +674,9 @@ how many objects it marks or sweeps for each object created. Larger values make the collector more aggressive. Beware that values too small can make the collector too slow to ever finish a cycle. -The default value is 300; the maximum value is 1000. +The default value is 200; the maximum value is 1000. +As a special case, a zero value means unlimited work, +effectively producing a non-incremental, stop-the-world collector. The garbage-collector step size controls the size of each incremental step, @@ -682,9 +684,7 @@ specifically how many objects the interpreter creates before performing a step. This parameter is logarithmic: A value of @M{n} means the interpreter will create @M{2@sp{n}} -objects between steps and perform equivalent work during the step. -A large value (e.g., 60) makes the collector a stop-the-world -(non-incremental) collector. +objects between steps. The default value is 8, which means steps of approximately @N{256 objects}. @@ -3306,43 +3306,43 @@ For options that need extra arguments, they are listed after the option. @description{ -@item{@id{LUA_GCCOLLECT}| +@item{@defid{LUA_GCCOLLECT}| Performs a full garbage-collection cycle. } -@item{@id{LUA_GCSTOP}| +@item{@defid{LUA_GCSTOP}| Stops the garbage collector. } -@item{@id{LUA_GCRESTART}| +@item{@defid{LUA_GCRESTART}| Restarts the garbage collector. } -@item{@id{LUA_GCCOUNT}| +@item{@defid{LUA_GCCOUNT}| Returns the current amount of memory (in Kbytes) in use by Lua. } -@item{@id{LUA_GCCOUNTB}| +@item{@defid{LUA_GCCOUNTB}| Returns the remainder of dividing the current amount of bytes of memory in use by Lua by 1024. } -@item{@id{LUA_GCSTEP}| +@item{@defid{LUA_GCSTEP}| Performs a step of garbage collection. } -@item{@id{LUA_GCISRUNNING}| +@item{@defid{LUA_GCISRUNNING}| Returns a boolean that tells whether the collector is running (i.e., not stopped). } -@item{@id{LUA_GCINC} (int pause, int stepmul, int stepsize)| +@item{@defid{LUA_GCINC} (int pause, int stepmul, int stepsize)| Changes the collector to incremental mode with the given parameters @see{incmode}. Returns the previous mode (@id{LUA_GCGEN} or @id{LUA_GCINC}). } -@item{@id{LUA_GCGEN} (int minormul, int minormajor, int majorminor)| +@item{@defid{LUA_GCGEN} (int minormul, int minormajor, int majorminor)| Changes the collector to generational mode with the given parameters @see{genmode}. Returns the previous mode (@id{LUA_GCGEN} or @id{LUA_GCINC}). diff --git a/testes/gengc.lua b/testes/gengc.lua index 3d4f67f8..d708d7fc 100644 --- a/testes/gengc.lua +++ b/testes/gengc.lua @@ -162,9 +162,16 @@ end assert(collectgarbage'isrunning') +do print"testing stop-the-world collection" + collectgarbage("incremental", nil, 0) --- just to make sure -assert(collectgarbage'isrunning') + -- each step does a complete cycle + assert(collectgarbage("step")) + assert(collectgarbage("step")) + + -- back to default value + collectgarbage("incremental", nil, 200) +end collectgarbage(oldmode)