First criteria for shifts minor<->major

This commit is contained in:
Roberto Ierusalimschy 2023-12-07 15:45:11 -03:00
parent 789e7acdea
commit 925fe8a0f2
8 changed files with 186 additions and 127 deletions

25
lapi.c
View File

@ -1204,26 +1204,29 @@ LUA_API int lua_gc (lua_State *L, int what, ...) {
break;
}
case LUA_GCGEN: {
unsigned int minormul = va_arg(argp, unsigned int);
unsigned int majormul = va_arg(argp, unsigned int);
int minormul = va_arg(argp, int);
int minormajor = va_arg(argp, int);
int majorminor = va_arg(argp, int);
res = (g->gckind == KGC_INC) ? LUA_GCINC : LUA_GCGEN;
if (minormul != 0)
if (minormul >= 0)
setgcparam(g, genminormul, minormul);
if (majormul != 0)
setgcparam(g, genmajormul, majormul);
if (minormajor >= 0)
setgcparam(g, minormajor, minormajor);
if (majorminor >= 0)
setgcparam(g, majorminor, majorminor);
luaC_changemode(L, KGC_GENMINOR);
break;
}
case LUA_GCINC: {
unsigned int pause = va_arg(argp, unsigned int);
unsigned int stepmul = va_arg(argp, unsigned int);
unsigned int stepsize = va_arg(argp, unsigned int);
int pause = va_arg(argp, int);
int stepmul = va_arg(argp, int);
int stepsize = va_arg(argp, int);
res = (g->gckind == KGC_INC) ? LUA_GCINC : LUA_GCGEN;
if (pause != 0)
if (pause >= 0)
setgcparam(g, gcpause, pause);
if (stepmul != 0)
if (stepmul >= 0)
setgcparam(g, gcstepmul, stepmul);
if (stepsize != 0)
if (stepsize >= 0)
g->gcstepsize = (stepsize <= log2maxs(l_obj)) ? stepsize
: log2maxs(l_obj);
luaC_changemode(L, KGC_INC);

View File

@ -224,14 +224,15 @@ static int luaB_collectgarbage (lua_State *L) {
return 1;
}
case LUA_GCGEN: {
int minormul = (int)luaL_optinteger(L, 2, 0);
int majormul = (int)luaL_optinteger(L, 3, 0);
return pushmode(L, lua_gc(L, o, minormul, majormul));
int minormul = (int)luaL_optinteger(L, 2, -1);
int majorminor = (int)luaL_optinteger(L, 3, -1);
int minormajor = (int)luaL_optinteger(L, 4, -1);
return pushmode(L, lua_gc(L, o, minormul, majorminor, minormajor));
}
case LUA_GCINC: {
int pause = (int)luaL_optinteger(L, 2, 0);
int stepmul = (int)luaL_optinteger(L, 3, 0);
int stepsize = (int)luaL_optinteger(L, 4, 0);
int pause = (int)luaL_optinteger(L, 2, -1);
int stepmul = (int)luaL_optinteger(L, 3, -1);
int stepsize = (int)luaL_optinteger(L, 4, -1);
return pushmode(L, lua_gc(L, o, pause, stepmul, stepsize));
}
default: {

79
lgc.c
View File

@ -1205,20 +1205,21 @@ static void correctgraylists (global_State *g) {
/*
** Mark black 'OLD1' objects when starting a new young collection.
** Gray objects are already in some gray list, and so will be visited in
** the atomic step. The counter 'GCmajorminor' keeps how many objects to
** become old before a major collection.
** the atomic step. Returns the number of objects that became old.
*/
static void markold (global_State *g, GCObject *from, GCObject *to) {
static l_obj markold (global_State *g, GCObject *from, GCObject *to) {
GCObject *p;
l_obj count = 0;
for (p = from; p != to; p = p->next) {
if (getage(p) == G_OLD1) {
lua_assert(!iswhite(p));
setage(p, G_OLD); /* now they are old */
g->GCmajorminor--; /* one more old object */
count++; /* one more old object */
if (isblack(p))
reallymarkobject(g, p);
}
}
return count;
}
@ -1240,7 +1241,7 @@ static void finishgencycle (lua_State *L, global_State *g) {
*/
static void atomic2major (lua_State *L, global_State *g) {
l_obj stepsize = cast(l_obj, 1) << g->gcstepsize;
g->GCmajorminor = gettotalobjs(g);
g->GCmajorminor = g->marked; /* number of live objects */
g->gckind = KGC_GENMAJOR;
g->reallyold = g->old1 = g->survival = NULL;
g->finobjrold = g->finobjold1 = g->finobjsur = NULL;
@ -1249,30 +1250,54 @@ static void atomic2major (lua_State *L, global_State *g) {
}
/*
** Decide whether to shift to major mode. It tests two conditions:
** 1) Whether the number of added old objects in this collection is more
** than half the number of new objects. ("step" is the number of objects
** created between minor collections. Except for forward barriers, it
** is the maximum number of objects that can become old in each minor
** collection.)
** 2) Whether the accumulated number of added old objects is larger
** than 'minormajor'% of the number of lived objects after the last
** major collection. (That percentage is computed in 'limit'.)
*/
static int checkminormajor (lua_State *L, global_State *g, l_obj addedold1) {
l_obj step = applygcparam(g, genminormul, g->GCmajorminor);
l_obj limit = applygcparam(g, minormajor, g->GCmajorminor);
//printf("-> major? %ld %ld %ld %ld (%ld)\n", g->marked, limit, step, addedold1, gettotalobjs(g));
if (addedold1 >= (step >> 1) || g->marked >= limit) {
atomic2major(L, g); /* go to major mode */
return 1;
}
return 0; /* stay in minor mode */
}
/*
** Does a young collection. First, mark 'OLD1' objects. Then does the
** atomic step. Then, check whether to continue in minor mode. If so,
** sweep all lists and advance pointers. Finally, finish the collection.
*/
static void youngcollection (lua_State *L, global_State *g) {
l_obj addedold1 = 0;
l_obj marked = g->marked; /* preserve 'g->marked' */
GCObject **psurvival; /* to point to first non-dead survival object */
GCObject *dummy; /* dummy out parameter to 'sweepgen' */
lua_assert(g->gcstate == GCSpropagate);
g->marked = 0;
if (g->firstold1) { /* are there regular OLD1 objects? */
markold(g, g->firstold1, g->reallyold); /* mark them */
addedold1 += markold(g, g->firstold1, g->reallyold); /* mark them */
g->firstold1 = NULL; /* no more OLD1 objects (for now) */
}
markold(g, g->finobj, g->finobjrold);
markold(g, g->tobefnz, NULL);
addedold1 += markold(g, g->finobj, g->finobjrold);
addedold1 += markold(g, g->tobefnz, NULL);
atomic(L);
atomic(L); /* will lose 'g->marked' */
/* keep total number of added old1 objects */
g->marked = marked + addedold1;
/* decide whether to shift to major mode */
if (g->GCmajorminor <= 0) { /* ?? */
atomic2major(L, g); /* go to major mode */
if (checkminormajor(L, g, addedold1))
return; /* nothing else to be done here */
}
/* sweep nursery and get a pointer to its last live element */
g->gcstate = GCSswpallgc;
@ -1319,7 +1344,8 @@ static void atomic2gen (lua_State *L, global_State *g) {
sweep2old(L, &g->tobefnz);
g->gckind = KGC_GENMINOR;
g->GCmajorminor = applygcparam(g, genmajormul, g->marked);
g->GCmajorminor = g->marked; /* "base" for number of objects */
g->marked = 0; /* to count the number of added old1 objects */
finishgencycle(L, g);
}
@ -1329,7 +1355,7 @@ static void atomic2gen (lua_State *L, global_State *g) {
** total number of objects grows 'genminormul'%.
*/
static void setminordebt (global_State *g) {
luaE_setdebt(g, applygcparam(g, genminormul, gettotalobjs(g)));
luaE_setdebt(g, applygcparam(g, genminormul, g->GCmajorminor));
}
@ -1369,13 +1395,11 @@ static void enterinc (global_State *g) {
*/
void luaC_changemode (lua_State *L, int newmode) {
global_State *g = G(L);
if (newmode != g->gckind) { /* does it need to change? */
if (newmode == KGC_INC) { /* entering incremental mode? */
if (g->gckind == KGC_GENMAJOR)
if (g->gckind == KGC_GENMAJOR) /* doing major collections? */
g->gckind = KGC_INC; /* already incremental but in name */
else
if (newmode != g->gckind) { /* does it need to change? */
if (newmode == KGC_INC) /* entering incremental mode? */
enterinc(g); /* entering incremental mode */
}
else {
lua_assert(newmode == KGC_GENMINOR);
entergen(L, g);
@ -1396,16 +1420,24 @@ static void fullgen (lua_State *L, global_State *g) {
/*
** After an atomic incremental step from a major collection,
** check whether collector could return to minor collections.
** It checks whether the number of objects 'tobecollected'
** is greater than 'majorminor'% of the number of objects added
** since the last collection ('addedobjs').
*/
static int checkmajorminor (lua_State *L, global_State *g) {
if (g->gckind == KGC_GENMAJOR) {
l_obj numobjs = gettotalobjs(g); /* current count */
if (g->marked < numobjs - (numobjs >> 2)) { /* ?? */
l_obj numobjs = gettotalobjs(g);
l_obj addedobjs = numobjs - g->GCmajorminor;
l_obj limit = applygcparam(g, majorminor, addedobjs);
l_obj tobecollected = numobjs - g->marked;
//printf("-> minor? %ld %ld %ld\n", tobecollected, limit, numobjs);
if (tobecollected > limit) {
atomic2gen(L, g); /* return to generational mode */
setminordebt(g);
return 0; /* exit incremental collection */
}
}
g->GCmajorminor = g->marked; /* prepare for next collection */
return 1; /* stay doing incremental collections */
}
@ -1634,8 +1666,6 @@ void luaC_step (lua_State *L) {
if (!gcrunning(g)) /* not running? */
luaE_setdebt(g, 2000);
else {
//printf("> step: %d %d %ld %ld -> ", g->gckind, g->gcstate, gettotalobjs(g), g->GCdebt);
switch (g->gckind) {
case KGC_INC: case KGC_GENMAJOR:
incstep(L, g);
@ -1645,7 +1675,6 @@ void luaC_step (lua_State *L) {
setminordebt(g);
break;
}
//printf("%d %d %ld %ld\n", g->gckind, g->gcstate, gettotalobjs(g), g->GCdebt);
}
}

48
lgc.h
View File

@ -161,10 +161,24 @@
/* Default Values for GC parameters */
/* generational */
/*
** Minor collections will shift to major ones after LUAI_MINORMAJOR%
** objects become old.
*/
#define LUAI_MINORMAJOR 100
/*
** Major collections will shift to minor ones after a collection
** collects at least LUAI_MAJORMINOR% of the new objects.
*/
#define LUAI_MAJORMINOR 80
/*
** A young (minor) collection will run after creating LUAI_GENMINORMUL%
** new objects.
*/
#define LUAI_GENMINORMUL 20
#define LUAI_GENMAJORMUL 100 /* major multiplier */
#define LUAI_GENMINORMUL 20 /* minor multiplier */
/* incremental */
@ -187,27 +201,17 @@
/*
** Macros to set and apply GC parameters. GC parameters are given in
** percentage points, but are stored as lu_byte. To reduce their
** values and avoid repeated divisions by 100, these macros store
** the original parameter multiplied by 2^n and divided by 100.
** To apply them, the value is divided by 2^n (a shift) and then
** multiplied by the stored parameter, yielding
** value / 2^n * (original parameter * 2^n / 100), or approximately
** (value * original parameter / 100).
**
** For most parameters, which are typically larger than 100%, 2^n is
** 16 (2^4), allowing maximum values up to ~1500%, with a granularity
** of ~6%. For the minor multiplier, which is typically smaller,
** 2^n is 64 (2^6) to allow more precision. In that case, the maximum
** value is ~400%, with a granularity of ~1.5%.
** percentage points, but are stored as lu_byte. To avoid repeated
** divisions by 100, these macros store the original parameter
** multiplied by 128 and divided by 100. To apply them, if it first
** divides the value by 128 it may lose precision; if it first
** multiplies by the parameter, it may overflow. So, it first divides
** by 32, then multiply by the parameter, and then divides the result by
** 4.
*/
#define gcparamshift(p) \
(offsetof(global_State, p) == offsetof(global_State, genminormul) ? 6 : 4)
#define setgcparam(g,p,v) \
(g->p = (cast_uint(v) << gcparamshift(p)) / 100u)
#define applygcparam(g,p,v) (((v) >> gcparamshift(p)) * g->p)
#define setgcparam(g,p,v) (g->gcp##p = (cast_uint(v) << 7) / 100u)
#define applygcparam(g,p,v) ((((v) >> 5) * g->gcp##p) >> 2)
/*

View File

@ -368,8 +368,9 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud, unsigned int seed) {
setgcparam(g, gcpause, LUAI_GCPAUSE);
setgcparam(g, gcstepmul, LUAI_GCMUL);
g->gcstepsize = LUAI_GCSTEPSIZE;
setgcparam(g, genmajormul, LUAI_GENMAJORMUL);
setgcparam(g, genminormul, LUAI_GENMINORMUL);
setgcparam(g, minormajor, LUAI_MINORMAJOR);
setgcparam(g, majorminor, LUAI_MAJORMINOR);
for (i=0; i < LUA_NUMTAGS; i++) g->mt[i] = NULL;
if (luaD_rawrunprotected(L, f_luaopen, NULL) != LUA_OK) {
/* memory allocation error: free partial state */

View File

@ -264,16 +264,17 @@ typedef struct global_State {
TValue l_registry;
TValue nilvalue; /* a nil value */
unsigned int seed; /* randomized seed for hashes */
unsigned short gcpgenminormul; /* control minor generational collections */
unsigned short gcpmajorminor; /* control shift major->minor */
unsigned short gcpminormajor; /* control shift minor->major */
unsigned short gcpgcpause; /* size of pause between successive GCs */
unsigned short gcpgcstepmul; /* GC "speed" */
lu_byte currentwhite;
lu_byte gcstate; /* state of garbage collector */
lu_byte gckind; /* kind of GC running */
lu_byte gcstopem; /* stops emergency collections */
lu_byte genminormul; /* control for minor generational collections */
lu_byte genmajormul; /* control for major generational collections */
lu_byte gcstp; /* control whether GC is running */
lu_byte gcemergency; /* true if this is an emergency collection */
lu_byte gcpause; /* size of pause between successive GCs */
lu_byte gcstepmul; /* GC "speed" */
lu_byte gcstepsize; /* (log2 of) GC granularity */
GCObject *allgc; /* list of all collectable objects */
GCObject **sweepgc; /* current position of sweep in list */

2
lua.c
View File

@ -646,7 +646,7 @@ static int pmain (lua_State *L) {
luai_openlibs(L); /* open standard libraries */
createargtable(L, argv, argc, script); /* create table 'arg' */
lua_gc(L, LUA_GCRESTART); /* start GC... */
lua_gc(L, LUA_GCGEN, 0, 0); /* ...in generational mode */
lua_gc(L, LUA_GCGEN, -1, -1, -1); /* ...in generational mode */
if (!(args & has_E)) { /* no option '-E'? */
if (handle_luainit(L) != LUA_OK) /* run LUA_INIT */
return 0; /* error running LUA_INIT */

View File

@ -621,7 +621,8 @@ that is inaccessible from Lua.
another live object refer to the object.)
Because Lua has no knowledge about @N{C code},
it never collects objects accessible through the registry @see{registry},
which includes the global environment @see{globalenv}.
which includes the global environment @see{globalenv} and
the main thread.
The garbage collector (GC) in Lua can work in two modes:
@ -638,8 +639,8 @@ therefore, optimal settings are also non-portable.
You can change the GC mode and parameters by calling
@Lid{lua_gc} @N{in C}
or @Lid{collectgarbage} in Lua.
You can also use these functions to control
the collector directly (e.g., to stop and restart it).
You can also use these functions to control the collector directly,
for instance to stop or restart it.
}
@ -656,39 +657,36 @@ and the @def{garbage-collector step size}.
The garbage-collector pause
controls how long the collector waits before starting a new cycle.
The collector starts a new cycle when the use of memory
hits @M{n%} of the use after the previous collection.
The collector starts a new cycle when the number of objects
hits @M{n%} of the total after the previous collection.
Larger values make the collector less aggressive.
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 memory in use
to double before starting 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 garbage-collector step multiplier
controls the speed of the collector relative to
memory allocation,
object creation,
that is,
how many elements it marks or sweeps for each
kilobyte of memory allocated.
Larger values make the collector more aggressive but also increase
the size of each incremental step.
You should not use values less than 100,
because they make the collector too slow and
can result in the collector never finishing a cycle.
The default value is 100; the maximum value is 1000.
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 garbage-collector step size controls the
size of each incremental step,
specifically how many bytes the interpreter allocates
specifically how many objects the interpreter creates
before performing a step.
This parameter is logarithmic:
A value of @M{n} means the interpreter will allocate @M{2@sp{n}}
bytes between steps and perform equivalent work during the step.
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.
The default value is 13,
which means steps of approximately @N{8 Kbytes}.
The default value is 8,
which means steps of approximately @N{256 objects}.
}
@ -697,31 +695,44 @@ which means steps of approximately @N{8 Kbytes}.
In generational mode,
the collector does frequent @emph{minor} collections,
which traverses only objects recently created.
If after a minor collection the use of memory is still above a limit,
the collector does a stop-the-world @emph{major} collection,
If after a minor collection the number of objects is above a limit,
the collector shifts to a @emph{major} collection,
which traverses all objects.
The generational mode uses two parameters:
the @def{minor multiplier} and the @def{the major multiplier}.
The collector will then stay doing major collections until
it detects that the program is generating enough garbage to justify
going back to minor collections.
The generational mode uses three parameters:
the @def{minor multiplier}, the @def{minor-major multiplier},
and the @def{major-minor multiplier}.
The minor multiplier controls the frequency of minor collections.
For a minor multiplier @M{x},
a new minor collection will be done when memory
grows @M{x%} larger than the memory in use after the previous major
collection.
a new minor collection will be done when the number of objects
grows @M{x%} larger than the number in use just after the last collection.
For instance, for a multiplier of 20,
the collector will do a minor collection when the use of memory
gets 20% larger than the use after the previous major collection.
The default value is 20; the maximum value is 200.
the collector will do a minor collection when the number of objects
gets 20% larger than the total after the last major collection.
The default value is 20.
The major multiplier controls the frequency of major collections.
For a major multiplier @M{x},
a new major collection will be done when memory
grows @M{x%} larger than the memory in use after the previous major
collection.
The minor-major multiplier controls the shift to major collections.
For a multiplier @M{x},
the collector will shift to a major collection
when the number of old objects grows @M{x%} larger
than the total after the previous major collection.
For instance, for a multiplier of 100,
the collector will do a major collection when the use of memory
gets larger than twice the use after the previous collection.
The default value is 100; the maximum value is 1000.
the collector will do a major collection when the number of old objects
gets larger than twice the total after the previous major collection.
The default value is 100.
The major-minor multiplier controls the shift back to minor collections.
For a multiplier @M{x},
the collector will shift back to minor collections
after a major collection collects at least @M{x%} of the allocated objects.
In particular, for a multiplier of 0,
the collector will immediately shift back to minor collections
after doing one cycle of major collections.
The default value is 20.
}
@ -3311,9 +3322,8 @@ Returns the remainder of dividing the current amount of bytes of
memory in use by Lua by 1024.
}
@item{@id{LUA_GCSTEP} @T{(int stepsize)}|
Performs an incremental step of garbage collection,
corresponding to the allocation of @id{stepsize} Kbytes.
@item{@id{LUA_GCSTEP}|
Performs a step of garbage collection.
}
@item{@id{LUA_GCISRUNNING}|
@ -3321,13 +3331,13 @@ Returns a boolean that tells whether the collector is running
(i.e., not stopped).
}
@item{@id{LUA_GCINC} (int pause, int stepmul, stepsize)|
@item{@id{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 majormul)|
@item{@id{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}).
@ -6312,13 +6322,14 @@ gives the exact number of bytes in use by Lua.
@item{@St{step}|
Performs a garbage-collection step.
The step @Q{size} is controlled by @id{arg}.
With a zero value,
the collector will perform one basic (indivisible) step.
For non-zero values,
the collector will perform as if that amount of memory
(in Kbytes) had been allocated by Lua.
Returns @true if the step finished a collection cycle.
In incremental mode,
that step corresponds to the current step size;
the function returns @true if the step finished a collection cycle.
In generational mode,
the step performs a full minor collection or
a major collection,
if the collector has scheduled one;
the function returns @true if the step performed a major collection.
}
@item{@St{isrunning}|
@ -6332,15 +6343,15 @@ This option can be followed by three numbers:
the garbage-collector pause,
the step multiplier,
and the step size @see{incmode}.
A zero means to not change that value.
A -1 or absent value means to not change that value.
}
@item{@St{generational}|
Change the collector mode to generational.
This option can be followed by two numbers:
the garbage-collector minor multiplier
and the major multiplier @see{genmode}.
A zero means to not change that value.
This option can be followed by three numbers:
the garbage-collector minor multiplier,
the minor-major multiplier, and the major-minor multiplier @see{genmode}.
A -1 or absent value means to not change that value.
}
}
@ -9229,6 +9240,9 @@ declare a local variable with the same name in the loop body.
@itemize{
@item{
There were several changes in the parameters
for the options @St{incremental} and @St{generational}
of the function @Lid{collectgarbage}.
}
}
@ -9245,6 +9259,12 @@ it is equivalent to @Lid{lua_closethread} with
@id{from} being @id{NULL}.
}
@item{
There were several changes in the parameters
for the options @Lid{LUA_GCINC} and @Lid{LUA_GCGEN}
of the function @Lid{lua_gc}.
}
}
}