diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2019-01-30 11:44:42 -0200 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2019-01-30 11:44:42 -0200 |
commit | 2c32bff60987d38a60a58d4f0123f3783da60a63 (patch) | |
tree | dd97348ac388dd6087c320994bdce6ff18be02e2 | |
parent | 264659bd53e92969a1e17d65c0266597cde24b5d (diff) | |
download | lua-2c32bff60987d38a60a58d4f0123f3783da60a63.tar.gz lua-2c32bff60987d38a60a58d4f0123f3783da60a63.tar.bz2 lua-2c32bff60987d38a60a58d4f0123f3783da60a63.zip |
After a "bad collections", avoid switching back back to generational
After a major bad collection (one that collects too few objects),
next collection will be major again. In that case, avoid switching
back to generational mode (as it will have to switch again to
incremental to do next major collection).
-rw-r--r-- | lapi.c | 6 | ||||
-rw-r--r-- | lgc.c | 158 | ||||
-rw-r--r-- | lgc.h | 9 | ||||
-rw-r--r-- | lstate.c | 4 | ||||
-rw-r--r-- | lstate.h | 1 | ||||
-rw-r--r-- | testes/gc.lua | 6 |
6 files changed, 134 insertions, 50 deletions
@@ -1141,22 +1141,21 @@ LUA_API int lua_gc (lua_State *L, int what, ...) { | |||
1141 | break; | 1141 | break; |
1142 | } | 1142 | } |
1143 | case LUA_GCGEN: { | 1143 | case LUA_GCGEN: { |
1144 | int oldmode = g->gckind; | ||
1145 | int minormul = va_arg(argp, int); | 1144 | int minormul = va_arg(argp, int); |
1146 | int majormul = va_arg(argp, int); | 1145 | int majormul = va_arg(argp, int); |
1146 | res = isdecGCmodegen(g) ? LUA_GCGEN : LUA_GCINC; | ||
1147 | if (minormul != 0) | 1147 | if (minormul != 0) |
1148 | g->genminormul = minormul; | 1148 | g->genminormul = minormul; |
1149 | if (majormul != 0) | 1149 | if (majormul != 0) |
1150 | setgcparam(g->genmajormul, majormul); | 1150 | setgcparam(g->genmajormul, majormul); |
1151 | luaC_changemode(L, KGC_GEN); | 1151 | luaC_changemode(L, KGC_GEN); |
1152 | res = (oldmode == KGC_GEN) ? LUA_GCGEN : LUA_GCINC; | ||
1153 | break; | 1152 | break; |
1154 | } | 1153 | } |
1155 | case LUA_GCINC: { | 1154 | case LUA_GCINC: { |
1156 | int oldmode = g->gckind; | ||
1157 | int pause = va_arg(argp, int); | 1155 | int pause = va_arg(argp, int); |
1158 | int stepmul = va_arg(argp, int); | 1156 | int stepmul = va_arg(argp, int); |
1159 | int stepsize = va_arg(argp, int); | 1157 | int stepsize = va_arg(argp, int); |
1158 | res = isdecGCmodegen(g) ? LUA_GCGEN : LUA_GCINC; | ||
1160 | if (pause != 0) | 1159 | if (pause != 0) |
1161 | setgcparam(g->gcpause, pause); | 1160 | setgcparam(g->gcpause, pause); |
1162 | if (stepmul != 0) | 1161 | if (stepmul != 0) |
@@ -1164,7 +1163,6 @@ LUA_API int lua_gc (lua_State *L, int what, ...) { | |||
1164 | if (stepsize != 0) | 1163 | if (stepsize != 0) |
1165 | g->gcstepsize = stepsize; | 1164 | g->gcstepsize = stepsize; |
1166 | luaC_changemode(L, KGC_INC); | 1165 | luaC_changemode(L, KGC_INC); |
1167 | res = (oldmode == KGC_GEN) ? LUA_GCGEN : LUA_GCINC; | ||
1168 | break; | 1166 | break; |
1169 | } | 1167 | } |
1170 | default: res = -1; /* invalid option */ | 1168 | default: res = -1; /* invalid option */ |
@@ -101,6 +101,7 @@ | |||
101 | 101 | ||
102 | static void reallymarkobject (global_State *g, GCObject *o); | 102 | static void reallymarkobject (global_State *g, GCObject *o); |
103 | static lu_mem atomic (lua_State *L); | 103 | static lu_mem atomic (lua_State *L); |
104 | static void entersweep (lua_State *L); | ||
104 | 105 | ||
105 | 106 | ||
106 | /* | 107 | /* |
@@ -1162,15 +1163,7 @@ static void youngcollection (lua_State *L, global_State *g) { | |||
1162 | } | 1163 | } |
1163 | 1164 | ||
1164 | 1165 | ||
1165 | /* | 1166 | static void atomic2gen (lua_State *L, global_State *g) { |
1166 | ** Enter generational mode. Must go until the end of an atomic cycle | ||
1167 | ** to ensure that all threads are in the gray list. Then, turn all | ||
1168 | ** objects into old and finishes the collection. | ||
1169 | */ | ||
1170 | static void entergen (lua_State *L, global_State *g) { | ||
1171 | luaC_runtilstate(L, bitmask(GCSpause)); /* prepare to start a new cycle */ | ||
1172 | luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */ | ||
1173 | atomic(L); | ||
1174 | /* sweep all elements making them old */ | 1167 | /* sweep all elements making them old */ |
1175 | sweep2old(L, &g->allgc); | 1168 | sweep2old(L, &g->allgc); |
1176 | /* everything alive now is old */ | 1169 | /* everything alive now is old */ |
@@ -1183,15 +1176,31 @@ static void entergen (lua_State *L, global_State *g) { | |||
1183 | sweep2old(L, &g->tobefnz); | 1176 | sweep2old(L, &g->tobefnz); |
1184 | 1177 | ||
1185 | g->gckind = KGC_GEN; | 1178 | g->gckind = KGC_GEN; |
1179 | g->lastatomic = 0; | ||
1186 | g->GCestimate = gettotalbytes(g); /* base for memory control */ | 1180 | g->GCestimate = gettotalbytes(g); /* base for memory control */ |
1187 | finishgencycle(L, g); | 1181 | finishgencycle(L, g); |
1188 | } | 1182 | } |
1189 | 1183 | ||
1190 | 1184 | ||
1191 | /* | 1185 | /* |
1186 | ** Enter generational mode. Must go until the end of an atomic cycle | ||
1187 | ** to ensure that all threads and weak tables are in the gray lists. | ||
1188 | ** Then, turn all objects into old and finishes the collection. | ||
1189 | */ | ||
1190 | static lu_mem entergen (lua_State *L, global_State *g) { | ||
1191 | lu_mem numobjs; | ||
1192 | luaC_runtilstate(L, bitmask(GCSpause)); /* prepare to start a new cycle */ | ||
1193 | luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */ | ||
1194 | numobjs = atomic(L); /* propagates all and then do the atomic stuff */ | ||
1195 | atomic2gen(L, g); | ||
1196 | return numobjs; | ||
1197 | } | ||
1198 | |||
1199 | |||
1200 | /* | ||
1192 | ** Enter incremental mode. Turn all objects white, make all | 1201 | ** Enter incremental mode. Turn all objects white, make all |
1193 | ** intermediate lists point to NULL (to avoid invalid pointers), | 1202 | ** intermediate lists point to NULL (to avoid invalid pointers), |
1194 | ** and go to pause state. | 1203 | ** and go to the pause state. |
1195 | */ | 1204 | */ |
1196 | static void enterinc (global_State *g) { | 1205 | static void enterinc (global_State *g) { |
1197 | whitelist(g, g->allgc); | 1206 | whitelist(g, g->allgc); |
@@ -1201,6 +1210,7 @@ static void enterinc (global_State *g) { | |||
1201 | g->finobjrold = g->finobjold = g->finobjsur = NULL; | 1210 | g->finobjrold = g->finobjold = g->finobjsur = NULL; |
1202 | g->gcstate = GCSpause; | 1211 | g->gcstate = GCSpause; |
1203 | g->gckind = KGC_INC; | 1212 | g->gckind = KGC_INC; |
1213 | g->lastatomic = 0; | ||
1204 | } | 1214 | } |
1205 | 1215 | ||
1206 | 1216 | ||
@@ -1215,54 +1225,114 @@ void luaC_changemode (lua_State *L, int newmode) { | |||
1215 | else | 1225 | else |
1216 | enterinc(g); /* entering incremental mode */ | 1226 | enterinc(g); /* entering incremental mode */ |
1217 | } | 1227 | } |
1228 | g->lastatomic = 0; | ||
1218 | } | 1229 | } |
1219 | 1230 | ||
1220 | 1231 | ||
1221 | /* | 1232 | /* |
1222 | ** Does a full collection in generational mode. | 1233 | ** Does a full collection in generational mode. |
1223 | */ | 1234 | */ |
1224 | static void fullgen (lua_State *L, global_State *g) { | 1235 | static lu_mem fullgen (lua_State *L, global_State *g) { |
1225 | enterinc(g); | 1236 | enterinc(g); |
1226 | entergen(L, g); | 1237 | return entergen(L, g); |
1238 | } | ||
1239 | |||
1240 | |||
1241 | /* | ||
1242 | ** Set debt for the next minor collection, which will happen when | ||
1243 | ** memory grows 'genminormul'%. | ||
1244 | */ | ||
1245 | static void setminordebt (global_State *g) { | ||
1246 | luaE_setdebt(g, -(cast(l_mem, (gettotalbytes(g) / 100)) * g->genminormul)); | ||
1227 | } | 1247 | } |
1228 | 1248 | ||
1229 | 1249 | ||
1230 | /* | 1250 | /* |
1231 | ** Does a generational "step". If memory grows 'genmajormul'% larger | 1251 | ** Does a major collection after last collection was a "bad collection". |
1232 | ** than last major collection (kept in 'g->GCestimate'), does a major | 1252 | ** |
1233 | ** collection. Otherwise, does a minor collection and set debt to make | 1253 | ** When the program is building a big struture, it allocates lots of |
1234 | ** another collection when memory grows 'genminormul'% larger. | 1254 | ** memory but generates very little garbage. In those scenarios, |
1235 | ** When it does a major collection, it then checks whether it could | 1255 | ** the generational mode just wastes time doing small collections, and |
1236 | ** reclaim at least ?? memory. If not, it sets a long pause for the | 1256 | ** major collections are frequently what we call a "bad collection", a |
1237 | ** next collection. (Therefore, the next collection will be a major | 1257 | ** collection that frees too few objects. To avoid the cost of switching |
1238 | ** one, too.) | 1258 | ** between generational mode and the incremental mode needed for full |
1259 | ** (major) collections, the collector tries to stay in incremental mode | ||
1260 | ** after a bad collection, and to switch back to generational mode only | ||
1261 | ** after a "good" collection (one that traverses less than 9/8 objects | ||
1262 | ** of the previous one). | ||
1263 | ** The collector must choose whether to stay in incremental mode or to | ||
1264 | ** switch back to generational mode before sweeping. At this point, it | ||
1265 | ** does not know the real memory in use, so it cannot use memory to | ||
1266 | ** decide whether to return to generational mode. Instead, it uses the | ||
1267 | ** number of objects traversed (returned by 'atomic') as a proxy. The | ||
1268 | ** field 'g->lastatomic' keeps this count from the last collection. | ||
1269 | ** ('g->lastatomic != 0' also means that the last collection was bad.) | ||
1270 | */ | ||
1271 | static void stepgenfull (lua_State *L, global_State *g) { | ||
1272 | lu_mem newatomic; /* count of traversed objects */ | ||
1273 | lu_mem lastatomic = g->lastatomic; /* count from last collection */ | ||
1274 | if (g->gckind == KGC_GEN) /* still in generational mode? */ | ||
1275 | enterinc(g); /* enter incremental mode */ | ||
1276 | luaC_runtilstate(L, bitmask(GCSpropagate)); /* start new cycle */ | ||
1277 | newatomic = atomic(L); /* mark everybody */ | ||
1278 | if (newatomic < lastatomic + (lastatomic >> 3)) { /* good collection? */ | ||
1279 | atomic2gen(L, g); /* return to generational mode */ | ||
1280 | setminordebt(g); | ||
1281 | } | ||
1282 | else { /* another bad collection; stay in incremental mode */ | ||
1283 | g->GCestimate = gettotalbytes(g); /* first estimate */; | ||
1284 | entersweep(L); | ||
1285 | luaC_runtilstate(L, bitmask(GCSpause)); /* finish collection */ | ||
1286 | setpause(g); | ||
1287 | g->lastatomic = newatomic; | ||
1288 | } | ||
1289 | } | ||
1290 | |||
1291 | |||
1292 | /* | ||
1293 | ** Does a generational "step". | ||
1294 | ** Usually, this means doing a minor collection and setting the debt to | ||
1295 | ** make another collection when memory grows 'genminormul'% larger. | ||
1296 | ** | ||
1297 | ** However, there are exceptions. If memory grows 'genmajormul'% | ||
1298 | ** larger than it was at the end of the last major collection (kept | ||
1299 | ** in 'g->GCestimate'), the function does a major collection. At the | ||
1300 | ** end, it checks whether the major collection was able to free a | ||
1301 | ** decent amount of memory (at least half the growth in memory since | ||
1302 | ** previous major collection). If so, the collector keeps its state, | ||
1303 | ** and the next collection will probably be minor again. Otherwise, | ||
1304 | ** we have what we call a "bad collection". In that case, set the field | ||
1305 | ** 'g->lastatomic' to signal that fact, so that the next collection will | ||
1306 | ** go to 'stepgenfull'. | ||
1307 | ** | ||
1239 | ** 'GCdebt <= 0' means an explicit call to GC step with "size" zero; | 1308 | ** 'GCdebt <= 0' means an explicit call to GC step with "size" zero; |
1240 | ** in that case, always do a minor collection. | 1309 | ** in that case, do a minor collection. |
1241 | */ | 1310 | */ |
1242 | static void genstep (lua_State *L, global_State *g) { | 1311 | static void genstep (lua_State *L, global_State *g) { |
1243 | lu_mem majorbase = g->GCestimate; /* memory after last major collection */ | 1312 | if (g->lastatomic != 0) /* last collection was a bad one? */ |
1244 | lu_mem majorinc = (majorbase / 100) * getgcparam(g->genmajormul); | 1313 | stepgenfull(L, g); /* do a full step */ |
1245 | lu_mem memnew = gettotalbytes(g); | 1314 | else { |
1246 | if (g->GCdebt > 0 && memnew > majorbase + majorinc) { | 1315 | lu_mem majorbase = g->GCestimate; /* memory after last major collection */ |
1247 | fullgen(L, g); | 1316 | lu_mem majorinc = (majorbase / 100) * getgcparam(g->genmajormul); |
1248 | memnew = gettotalbytes(g); | 1317 | if (g->GCdebt > 0 && gettotalbytes(g) > majorbase + majorinc) { |
1249 | if (memnew < majorbase + (majorinc / 2)) { | 1318 | lu_mem numobjs = fullgen(L, g); /* do a major collection */ |
1250 | /* collected at least half of memory growth since last major | 1319 | if (gettotalbytes(g) < majorbase + (majorinc / 2)) { |
1251 | collection; go back to minor collections */ | 1320 | /* collected at least half of memory growth since last major |
1252 | luaE_setdebt(g, -(cast(l_mem, (memnew / 100)) * g->genminormul)); | 1321 | collection; keep doing minor collections */ |
1322 | setminordebt(g); | ||
1323 | } | ||
1324 | else { /* bad collection */ | ||
1325 | g->lastatomic = numobjs; /* signal that last collection was bad */ | ||
1326 | setpause(g); /* do a long wait for next (major) collection */ | ||
1327 | } | ||
1253 | } | 1328 | } |
1254 | else { | 1329 | else { /* regular case; do a minor collection */ |
1255 | /* memory seems to be growing; do a long wait for next (major) | 1330 | youngcollection(L, g); |
1256 | collection */ | 1331 | setminordebt(g); |
1257 | setpause(g); | 1332 | g->GCestimate = majorbase; /* preserve base value */ |
1258 | } | 1333 | } |
1259 | } | 1334 | } |
1260 | else { | 1335 | lua_assert(isdecGCmodegen(g)); |
1261 | youngcollection(L, g); | ||
1262 | memnew = gettotalbytes(g); | ||
1263 | luaE_setdebt(g, -(cast(l_mem, (memnew / 100)) * g->genminormul)); | ||
1264 | g->GCestimate = majorbase; /* preserve base value */ | ||
1265 | } | ||
1266 | } | 1336 | } |
1267 | 1337 | ||
1268 | /* }====================================================== */ | 1338 | /* }====================================================== */ |
@@ -1493,10 +1563,10 @@ static void incstep (lua_State *L, global_State *g) { | |||
1493 | void luaC_step (lua_State *L) { | 1563 | void luaC_step (lua_State *L) { |
1494 | global_State *g = G(L); | 1564 | global_State *g = G(L); |
1495 | if (g->gcrunning) { /* running? */ | 1565 | if (g->gcrunning) { /* running? */ |
1496 | if (g->gckind == KGC_INC) | 1566 | if(isdecGCmodegen(g)) |
1497 | incstep(L, g); | ||
1498 | else | ||
1499 | genstep(L, g); | 1567 | genstep(L, g); |
1568 | else | ||
1569 | incstep(L, g); | ||
1500 | } | 1570 | } |
1501 | } | 1571 | } |
1502 | 1572 | ||
@@ -123,7 +123,7 @@ | |||
123 | #define LUAI_GENMINORMUL 20 | 123 | #define LUAI_GENMINORMUL 20 |
124 | 124 | ||
125 | /* wait memory to double before starting new cycle */ | 125 | /* wait memory to double before starting new cycle */ |
126 | #define LUAI_GCPAUSE 200 /* 200% */ | 126 | #define LUAI_GCPAUSE 200 |
127 | 127 | ||
128 | /* | 128 | /* |
129 | ** some gc parameters are stored divided by 4 to allow a maximum value | 129 | ** some gc parameters are stored divided by 4 to allow a maximum value |
@@ -139,6 +139,13 @@ | |||
139 | 139 | ||
140 | 140 | ||
141 | /* | 141 | /* |
142 | ** Check whether the declared GC mode is generational. While in | ||
143 | ** generational mode, the collector can go temporarily to incremental | ||
144 | ** mode to improve performance. This is signaled by 'g->lastatomic != 0'. | ||
145 | */ | ||
146 | #define isdecGCmodegen(g) (g->gckind == KGC_GEN || g->lastatomic != 0) | ||
147 | |||
148 | /* | ||
142 | ** Does one step of collection when debt becomes positive. 'pre'/'pos' | 149 | ** Does one step of collection when debt becomes positive. 'pre'/'pos' |
143 | ** allows some adjustments to be done only when needed. macro | 150 | ** allows some adjustments to be done only when needed. macro |
144 | ** 'condchangemem' is used only for heavy tests (forcing a full | 151 | ** 'condchangemem' is used only for heavy tests (forcing a full |
@@ -233,7 +233,8 @@ static void init_registry (lua_State *L, global_State *g) { | |||
233 | 233 | ||
234 | /* | 234 | /* |
235 | ** open parts of the state that may cause memory-allocation errors. | 235 | ** open parts of the state that may cause memory-allocation errors. |
236 | ** ('ttisnil(&g->nilvalue)'' flags that the state was completely build) | 236 | ** ('g->nilvalue' being a nil value flags that the state was completely |
237 | ** build.) | ||
237 | */ | 238 | */ |
238 | static void f_luaopen (lua_State *L, void *ud) { | 239 | static void f_luaopen (lua_State *L, void *ud) { |
239 | global_State *g = G(L); | 240 | global_State *g = G(L); |
@@ -386,6 +387,7 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { | |||
386 | g->twups = NULL; | 387 | g->twups = NULL; |
387 | g->totalbytes = sizeof(LG); | 388 | g->totalbytes = sizeof(LG); |
388 | g->GCdebt = 0; | 389 | g->GCdebt = 0; |
390 | g->lastatomic = 0; | ||
389 | setivalue(&g->nilvalue, 0); /* to signal that state is not yet built */ | 391 | setivalue(&g->nilvalue, 0); /* to signal that state is not yet built */ |
390 | setgcparam(g->gcpause, LUAI_GCPAUSE); | 392 | setgcparam(g->gcpause, LUAI_GCPAUSE); |
391 | setgcparam(g->gcstepmul, LUAI_GCMUL); | 393 | setgcparam(g->gcstepmul, LUAI_GCMUL); |
@@ -193,6 +193,7 @@ typedef struct global_State { | |||
193 | l_mem totalbytes; /* number of bytes currently allocated - GCdebt */ | 193 | l_mem totalbytes; /* number of bytes currently allocated - GCdebt */ |
194 | l_mem GCdebt; /* bytes allocated not yet compensated by the collector */ | 194 | l_mem GCdebt; /* bytes allocated not yet compensated by the collector */ |
195 | lu_mem GCestimate; /* an estimate of the non-garbage memory in use */ | 195 | lu_mem GCestimate; /* an estimate of the non-garbage memory in use */ |
196 | lu_mem lastatomic; /* see function 'genstep' in file 'lgc.c' */ | ||
196 | stringtable strt; /* hash table for strings */ | 197 | stringtable strt; /* hash table for strings */ |
197 | TValue l_registry; | 198 | TValue l_registry; |
198 | TValue nilvalue; /* a nil value */ | 199 | TValue nilvalue; /* a nil value */ |
diff --git a/testes/gc.lua b/testes/gc.lua index 84e8ffb7..05bf564e 100644 --- a/testes/gc.lua +++ b/testes/gc.lua | |||
@@ -11,6 +11,12 @@ collectgarbage() | |||
11 | 11 | ||
12 | local oldmode = collectgarbage("incremental") | 12 | local oldmode = collectgarbage("incremental") |
13 | 13 | ||
14 | -- changing modes should return previous mode | ||
15 | assert(collectgarbage("generational") == "incremental") | ||
16 | assert(collectgarbage("generational") == "generational") | ||
17 | assert(collectgarbage("incremental") == "generational") | ||
18 | assert(collectgarbage("incremental") == "incremental") | ||
19 | |||
14 | 20 | ||
15 | local function gcinfo () | 21 | local function gcinfo () |
16 | return collectgarbage"count" * 1024 | 22 | return collectgarbage"count" * 1024 |