diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2018-11-01 13:21:00 -0300 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2018-11-01 13:21:00 -0300 |
commit | e8c779736f3029df353038352c14c8ab63728811 (patch) | |
tree | 23f75fa455ec333677650198cd889b187228b735 | |
parent | 2fc6b55dae7a120b4272ca0e9c356d1f96053bd9 (diff) | |
download | lua-e8c779736f3029df353038352c14c8ab63728811.tar.gz lua-e8c779736f3029df353038352c14c8ab63728811.tar.bz2 lua-e8c779736f3029df353038352c14c8ab63728811.zip |
Removed internal cache for closures
The mechanism of "caching the last closure created for a prototype to
try to reuse it the next time a closure for that prototype is created"
was removed. There are several reasons:
- It is hard to find a natural example where this cache has a measurable
impact on performance.
- Programmers already perceive closure creation as something slow,
so they tend to avoid it inside hot paths. (Any case where the cache
could reuse a closure can be rewritten predefining the closure in some
variable and using that variable.)
- The implementation was somewhat complex, due to a bad interaction
with the generational collector. (Typically, new closures are new,
while prototypes are old. So, the cache breaks the invariant that
old objects should not point to new ones.)
-rw-r--r-- | lfunc.c | 2 | ||||
-rw-r--r-- | lgc.c | 64 | ||||
-rw-r--r-- | lgc.h | 4 | ||||
-rw-r--r-- | lobject.h | 2 | ||||
-rw-r--r-- | lstate.c | 2 | ||||
-rw-r--r-- | lstate.h | 3 | ||||
-rw-r--r-- | ltests.c | 4 | ||||
-rw-r--r-- | lvm.c | 39 | ||||
-rw-r--r-- | testes/closure.lua | 13 |
9 files changed, 10 insertions, 123 deletions
@@ -219,8 +219,6 @@ Proto *luaF_newproto (lua_State *L) { | |||
219 | f->p = NULL; | 219 | f->p = NULL; |
220 | f->sizep = 0; | 220 | f->sizep = 0; |
221 | f->code = NULL; | 221 | f->code = NULL; |
222 | f->cache = NULL; | ||
223 | f->cachemiss = 0; | ||
224 | f->sizecode = 0; | 222 | f->sizecode = 0; |
225 | f->lineinfo = NULL; | 223 | f->lineinfo = NULL; |
226 | f->sizelineinfo = 0; | 224 | f->sizelineinfo = 0; |
@@ -221,27 +221,6 @@ void luaC_barrierback_ (lua_State *L, GCObject *o) { | |||
221 | } | 221 | } |
222 | 222 | ||
223 | 223 | ||
224 | /* | ||
225 | ** Barrier for prototype's cache of closures. It turns the prototype | ||
226 | ** back to gray (it was black). For an 'OLD1' prototype, making it | ||
227 | ** gray stops it from being visited by 'markold', so it is linked in | ||
228 | ** the 'grayagain' list to ensure it will be visited. For other ages, | ||
229 | ** it goes to the 'protogray' list, as only its 'cache' field needs to | ||
230 | ** be revisited. (A prototype to be in this barrier must be already | ||
231 | ** finished, so its other fields cannot change and do not need to be | ||
232 | ** revisited.) | ||
233 | */ | ||
234 | LUAI_FUNC void luaC_protobarrier_ (lua_State *L, Proto *p) { | ||
235 | global_State *g = G(L); | ||
236 | lua_assert(g->gckind != KGC_GEN || isold(p)); | ||
237 | if (getage(p) == G_OLD1) /* still need to be visited? */ | ||
238 | linkgclist(p, g->grayagain); /* link it in 'grayagain' */ | ||
239 | else | ||
240 | linkgclist(p, g->protogray); /* link it in 'protogray' */ | ||
241 | black2gray(p); /* make prototype gray */ | ||
242 | } | ||
243 | |||
244 | |||
245 | void luaC_fix (lua_State *L, GCObject *o) { | 224 | void luaC_fix (lua_State *L, GCObject *o) { |
246 | global_State *g = G(L); | 225 | global_State *g = G(L); |
247 | lua_assert(g->allgc == o); /* object must be 1st in 'allgc' list! */ | 226 | lua_assert(g->allgc == o); /* object must be 1st in 'allgc' list! */ |
@@ -379,7 +358,7 @@ static int remarkupvals (global_State *g) { | |||
379 | */ | 358 | */ |
380 | static void restartcollection (global_State *g) { | 359 | static void restartcollection (global_State *g) { |
381 | g->gray = g->grayagain = NULL; | 360 | g->gray = g->grayagain = NULL; |
382 | g->weak = g->allweak = g->ephemeron = g->protogray = NULL; | 361 | g->weak = g->allweak = g->ephemeron = NULL; |
383 | markobject(g, g->mainthread); | 362 | markobject(g, g->mainthread); |
384 | markvalue(g, &g->l_registry); | 363 | markvalue(g, &g->l_registry); |
385 | markmt(g); | 364 | markmt(g); |
@@ -535,39 +514,12 @@ static int traverseudata (global_State *g, Udata *u) { | |||
535 | 514 | ||
536 | 515 | ||
537 | /* | 516 | /* |
538 | ** Check the cache of a prototype, to keep invariants. If the | ||
539 | ** cache is white, clear it. (A cache should not prevent the | ||
540 | ** collection of its reference.) Otherwise, if in generational | ||
541 | ** mode, check the generational invariant. If the cache is old, | ||
542 | ** everything is ok. If the prototype is 'OLD0', everything | ||
543 | ** is ok too. (It will naturally be visited again.) If the | ||
544 | ** prototype is older than 'OLD0', then its cache (which is new) | ||
545 | ** must be visited again in the next collection, so the prototype | ||
546 | ** goes to the 'protogray' list. (If the prototype has a cache, | ||
547 | ** it is already immutable and does not need other barriers; | ||
548 | ** then, it can become gray without problems for its other fields.) | ||
549 | */ | ||
550 | static void checkprotocache (global_State *g, Proto *p) { | ||
551 | if (p->cache) { | ||
552 | if (iswhite(p->cache)) | ||
553 | p->cache = NULL; /* allow cache to be collected */ | ||
554 | else if (g->gckind == KGC_GEN && !isold(p->cache) && getage(p) >= G_OLD1) { | ||
555 | linkgclist(p, g->protogray); /* link it in 'protogray' */ | ||
556 | black2gray(p); /* make prototype gray */ | ||
557 | } | ||
558 | } | ||
559 | p->cachemiss = 0; /* restart counting */ | ||
560 | } | ||
561 | |||
562 | |||
563 | /* | ||
564 | ** Traverse a prototype. (While a prototype is being build, its | 517 | ** Traverse a prototype. (While a prototype is being build, its |
565 | ** arrays can be larger than needed; the extra slots are filled with | 518 | ** arrays can be larger than needed; the extra slots are filled with |
566 | ** NULL, so the use of 'markobjectN') | 519 | ** NULL, so the use of 'markobjectN') |
567 | */ | 520 | */ |
568 | static int traverseproto (global_State *g, Proto *f) { | 521 | static int traverseproto (global_State *g, Proto *f) { |
569 | int i; | 522 | int i; |
570 | checkprotocache(g, f); | ||
571 | markobjectN(g, f->source); | 523 | markobjectN(g, f->source); |
572 | for (i = 0; i < f->sizek; i++) /* mark literals */ | 524 | for (i = 0; i < f->sizek; i++) /* mark literals */ |
573 | markvalue(g, &f->k[i]); | 525 | markvalue(g, &f->k[i]); |
@@ -696,19 +648,6 @@ static void convergeephemerons (global_State *g) { | |||
696 | ** ======================================================= | 648 | ** ======================================================= |
697 | */ | 649 | */ |
698 | 650 | ||
699 | static void clearprotolist (global_State *g) { | ||
700 | GCObject *p = g->protogray; | ||
701 | g->protogray = NULL; | ||
702 | while (p != NULL) { | ||
703 | Proto *pp = gco2p(p); | ||
704 | GCObject *next = pp->gclist; | ||
705 | lua_assert(isgray(pp) && (pp->cache != NULL || pp->cachemiss >= MAXMISS)); | ||
706 | gray2black(pp); | ||
707 | checkprotocache(g, pp); | ||
708 | p = next; | ||
709 | } | ||
710 | } | ||
711 | |||
712 | 651 | ||
713 | /* | 652 | /* |
714 | ** clear entries with unmarked keys from all weaktables in list 'l' | 653 | ** clear entries with unmarked keys from all weaktables in list 'l' |
@@ -1439,7 +1378,6 @@ static lu_mem atomic (lua_State *L) { | |||
1439 | clearbyvalues(g, g->weak, origweak); | 1378 | clearbyvalues(g, g->weak, origweak); |
1440 | clearbyvalues(g, g->allweak, origall); | 1379 | clearbyvalues(g, g->allweak, origall); |
1441 | luaS_clearcache(g); | 1380 | luaS_clearcache(g); |
1442 | clearprotolist(g); | ||
1443 | g->currentwhite = cast_byte(otherwhite(g)); /* flip current white */ | 1381 | g->currentwhite = cast_byte(otherwhite(g)); /* flip current white */ |
1444 | lua_assert(g->gray == NULL); | 1382 | lua_assert(g->gray == NULL); |
1445 | return work; /* estimate of slots marked by 'atomic' */ | 1383 | return work; /* estimate of slots marked by 'atomic' */ |
@@ -164,9 +164,6 @@ | |||
164 | (isblack(p) && iswhite(o)) ? \ | 164 | (isblack(p) && iswhite(o)) ? \ |
165 | luaC_barrier_(L,obj2gco(p),obj2gco(o)) : cast_void(0)) | 165 | luaC_barrier_(L,obj2gco(p),obj2gco(o)) : cast_void(0)) |
166 | 166 | ||
167 | #define luaC_protobarrier(L,p,o) \ | ||
168 | (isblack(p) ? luaC_protobarrier_(L,p) : cast_void(0)) | ||
169 | |||
170 | LUAI_FUNC void luaC_fix (lua_State *L, GCObject *o); | 167 | LUAI_FUNC void luaC_fix (lua_State *L, GCObject *o); |
171 | LUAI_FUNC void luaC_freeallobjects (lua_State *L); | 168 | LUAI_FUNC void luaC_freeallobjects (lua_State *L); |
172 | LUAI_FUNC void luaC_step (lua_State *L); | 169 | LUAI_FUNC void luaC_step (lua_State *L); |
@@ -175,7 +172,6 @@ LUAI_FUNC void luaC_fullgc (lua_State *L, int isemergency); | |||
175 | LUAI_FUNC GCObject *luaC_newobj (lua_State *L, int tt, size_t sz); | 172 | LUAI_FUNC GCObject *luaC_newobj (lua_State *L, int tt, size_t sz); |
176 | LUAI_FUNC void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v); | 173 | LUAI_FUNC void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v); |
177 | LUAI_FUNC void luaC_barrierback_ (lua_State *L, GCObject *o); | 174 | LUAI_FUNC void luaC_barrierback_ (lua_State *L, GCObject *o); |
178 | LUAI_FUNC void luaC_protobarrier_ (lua_State *L, Proto *p); | ||
179 | LUAI_FUNC void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt); | 175 | LUAI_FUNC void luaC_checkfinalizer (lua_State *L, GCObject *o, Table *mt); |
180 | LUAI_FUNC void luaC_changemode (lua_State *L, int newmode); | 176 | LUAI_FUNC void luaC_changemode (lua_State *L, int newmode); |
181 | 177 | ||
@@ -505,7 +505,6 @@ typedef struct Proto { | |||
505 | lu_byte numparams; /* number of fixed (named) parameters */ | 505 | lu_byte numparams; /* number of fixed (named) parameters */ |
506 | lu_byte is_vararg; | 506 | lu_byte is_vararg; |
507 | lu_byte maxstacksize; /* number of registers needed by this function */ | 507 | lu_byte maxstacksize; /* number of registers needed by this function */ |
508 | lu_byte cachemiss; /* count for successive misses for 'cache' field */ | ||
509 | int sizeupvalues; /* size of 'upvalues' */ | 508 | int sizeupvalues; /* size of 'upvalues' */ |
510 | int sizek; /* size of 'k' */ | 509 | int sizek; /* size of 'k' */ |
511 | int sizecode; | 510 | int sizecode; |
@@ -516,7 +515,6 @@ typedef struct Proto { | |||
516 | int linedefined; /* debug information */ | 515 | int linedefined; /* debug information */ |
517 | int lastlinedefined; /* debug information */ | 516 | int lastlinedefined; /* debug information */ |
518 | TValue *k; /* constants used by the function */ | 517 | TValue *k; /* constants used by the function */ |
519 | struct LClosure *cache; /* last-created closure with this prototype */ | ||
520 | Instruction *code; /* opcodes */ | 518 | Instruction *code; /* opcodes */ |
521 | struct Proto **p; /* functions defined inside the function */ | 519 | struct Proto **p; /* functions defined inside the function */ |
522 | Upvaldesc *upvalues; /* upvalue information */ | 520 | Upvaldesc *upvalues; /* upvalue information */ |
@@ -340,7 +340,7 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { | |||
340 | g->finobjsur = g->finobjold = g->finobjrold = NULL; | 340 | g->finobjsur = g->finobjold = g->finobjrold = NULL; |
341 | g->sweepgc = NULL; | 341 | g->sweepgc = NULL; |
342 | g->gray = g->grayagain = NULL; | 342 | g->gray = g->grayagain = NULL; |
343 | g->weak = g->ephemeron = g->allweak = g->protogray = NULL; | 343 | g->weak = g->ephemeron = g->allweak = NULL; |
344 | g->twups = NULL; | 344 | g->twups = NULL; |
345 | g->totalbytes = sizeof(LG); | 345 | g->totalbytes = sizeof(LG); |
346 | g->GCdebt = 0; | 346 | g->GCdebt = 0; |
@@ -43,8 +43,6 @@ | |||
43 | ** 'weak': tables with weak values to be cleared; | 43 | ** 'weak': tables with weak values to be cleared; |
44 | ** 'ephemeron': ephemeron tables with white->white entries; | 44 | ** 'ephemeron': ephemeron tables with white->white entries; |
45 | ** 'allweak': tables with weak keys and/or weak values to be cleared. | 45 | ** 'allweak': tables with weak keys and/or weak values to be cleared. |
46 | ** There is also a list 'protogray' for prototypes that need to have | ||
47 | ** their caches cleared. | ||
48 | 46 | ||
49 | */ | 47 | */ |
50 | 48 | ||
@@ -186,7 +184,6 @@ typedef struct global_State { | |||
186 | GCObject *weak; /* list of tables with weak values */ | 184 | GCObject *weak; /* list of tables with weak values */ |
187 | GCObject *ephemeron; /* list of ephemeron tables (weak keys) */ | 185 | GCObject *ephemeron; /* list of ephemeron tables (weak keys) */ |
188 | GCObject *allweak; /* list of all-weak tables */ | 186 | GCObject *allweak; /* list of all-weak tables */ |
189 | GCObject *protogray; /* list of prototypes with "new" caches */ | ||
190 | GCObject *tobefnz; /* list of userdata to be GC */ | 187 | GCObject *tobefnz; /* list of userdata to be GC */ |
191 | GCObject *fixedgc; /* list of objects not to be collected */ | 188 | GCObject *fixedgc; /* list of objects not to be collected */ |
192 | /* fields for generational collector */ | 189 | /* fields for generational collector */ |
@@ -283,7 +283,6 @@ static void checkudata (global_State *g, Udata *u) { | |||
283 | static void checkproto (global_State *g, Proto *f) { | 283 | static void checkproto (global_State *g, Proto *f) { |
284 | int i; | 284 | int i; |
285 | GCObject *fgc = obj2gco(f); | 285 | GCObject *fgc = obj2gco(f); |
286 | checkobjref(g, fgc, f->cache); | ||
287 | checkobjref(g, fgc, f->source); | 286 | checkobjref(g, fgc, f->source); |
288 | for (i=0; i<f->sizek; i++) { | 287 | for (i=0; i<f->sizek; i++) { |
289 | if (ttisstring(f->k + i)) | 288 | if (ttisstring(f->k + i)) |
@@ -417,8 +416,6 @@ static void checkobject (global_State *g, GCObject *o, int maybedead, | |||
417 | getage(o) == G_TOUCHED1 || | 416 | getage(o) == G_TOUCHED1 || |
418 | getage(o) == G_OLD0 || | 417 | getage(o) == G_OLD0 || |
419 | o->tt == LUA_TTHREAD || | 418 | o->tt == LUA_TTHREAD || |
420 | (o->tt == LUA_TPROTO && | ||
421 | (gco2p(o)->cache != NULL || gco2p(o)->cachemiss >= MAXMISS)) || | ||
422 | (o->tt == LUA_TUPVAL && upisopen(gco2upv(o)))); | 419 | (o->tt == LUA_TUPVAL && upisopen(gco2upv(o)))); |
423 | } | 420 | } |
424 | } | 421 | } |
@@ -452,7 +449,6 @@ static void checkgrays (global_State *g) { | |||
452 | checkgraylist(g, g->grayagain); | 449 | checkgraylist(g, g->grayagain); |
453 | checkgraylist(g, g->weak); | 450 | checkgraylist(g, g->weak); |
454 | checkgraylist(g, g->ephemeron); | 451 | checkgraylist(g, g->ephemeron); |
455 | checkgraylist(g, g->protogray); | ||
456 | } | 452 | } |
457 | 453 | ||
458 | 454 | ||
@@ -684,30 +684,8 @@ lua_Integer luaV_shiftl (lua_Integer x, lua_Integer y) { | |||
684 | 684 | ||
685 | 685 | ||
686 | /* | 686 | /* |
687 | ** check whether cached closure in prototype 'p' may be reused, that is, | ||
688 | ** whether there is a cached closure with the same upvalues needed by | ||
689 | ** new closure to be created. | ||
690 | */ | ||
691 | static LClosure *getcached (Proto *p, UpVal **encup, StkId base) { | ||
692 | LClosure *c = p->cache; | ||
693 | if (c != NULL) { /* is there a cached closure? */ | ||
694 | int nup = p->sizeupvalues; | ||
695 | Upvaldesc *uv = p->upvalues; | ||
696 | int i; | ||
697 | for (i = 0; i < nup; i++) { /* check whether it has right upvalues */ | ||
698 | TValue *v = uv[i].instack ? s2v(base + uv[i].idx) : encup[uv[i].idx]->v; | ||
699 | if (c->upvals[i]->v != v) | ||
700 | return NULL; /* wrong upvalue; cannot reuse closure */ | ||
701 | } | ||
702 | p->cachemiss = 0; /* got a hit */ | ||
703 | } | ||
704 | return c; /* return cached closure (or NULL if no cached closure) */ | ||
705 | } | ||
706 | |||
707 | |||
708 | /* | ||
709 | ** create a new Lua closure, push it in the stack, and initialize | 687 | ** create a new Lua closure, push it in the stack, and initialize |
710 | ** its upvalues. ??? | 688 | ** its upvalues. |
711 | */ | 689 | */ |
712 | static void pushclosure (lua_State *L, Proto *p, UpVal **encup, StkId base, | 690 | static void pushclosure (lua_State *L, Proto *p, UpVal **encup, StkId base, |
713 | StkId ra) { | 691 | StkId ra) { |
@@ -724,13 +702,6 @@ static void pushclosure (lua_State *L, Proto *p, UpVal **encup, StkId base, | |||
724 | ncl->upvals[i] = encup[uv[i].idx]; | 702 | ncl->upvals[i] = encup[uv[i].idx]; |
725 | luaC_objbarrier(L, ncl, ncl->upvals[i]); | 703 | luaC_objbarrier(L, ncl, ncl->upvals[i]); |
726 | } | 704 | } |
727 | if (p->cachemiss >= MAXMISS) /* too many missings? */ | ||
728 | p->cache = NULL; /* give up cache */ | ||
729 | else { | ||
730 | p->cache = ncl; /* save it on cache for reuse */ | ||
731 | luaC_protobarrier(L, p, ncl); | ||
732 | p->cachemiss++; | ||
733 | } | ||
734 | } | 705 | } |
735 | 706 | ||
736 | 707 | ||
@@ -1811,13 +1782,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { | |||
1811 | } | 1782 | } |
1812 | vmcase(OP_CLOSURE) { | 1783 | vmcase(OP_CLOSURE) { |
1813 | Proto *p = cl->p->p[GETARG_Bx(i)]; | 1784 | Proto *p = cl->p->p[GETARG_Bx(i)]; |
1814 | LClosure *ncl = getcached(p, cl->upvals, base); /* cached closure */ | 1785 | halfProtect(pushclosure(L, p, cl->upvals, base, ra)); |
1815 | if (ncl == NULL) { /* no match? */ | ||
1816 | savestate(L, ci); /* in case of allocation errors */ | ||
1817 | pushclosure(L, p, cl->upvals, base, ra); /* create a new one */ | ||
1818 | } | ||
1819 | else | ||
1820 | setclLvalue2s(L, ra, ncl); /* push cashed closure */ | ||
1821 | checkGC(L, ra + 1); | 1786 | checkGC(L, ra + 1); |
1822 | vmbreak; | 1787 | vmbreak; |
1823 | } | 1788 | } |
diff --git a/testes/closure.lua b/testes/closure.lua index 5d090d91..cdeaebaa 100644 --- a/testes/closure.lua +++ b/testes/closure.lua | |||
@@ -44,18 +44,17 @@ assert(B.g == 19) | |||
44 | 44 | ||
45 | -- testing equality | 45 | -- testing equality |
46 | a = {} | 46 | a = {} |
47 | collectgarbage"stop" | ||
48 | for i = 1, 5 do a[i] = function (x) return x + a + _ENV end end | ||
49 | collectgarbage"restart" | ||
50 | assert(a[3] == a[4] and a[4] == a[5]) | ||
51 | 47 | ||
52 | for i = 1, 5 do a[i] = function (x) return i + a + _ENV end end | 48 | for i = 1, 5 do a[i] = function (x) return i + a + _ENV end end |
53 | assert(a[3] ~= a[4] and a[4] ~= a[5]) | 49 | assert(a[3] ~= a[4] and a[4] ~= a[5]) |
54 | 50 | ||
55 | local function f() | 51 | do |
56 | return function (x) return math.sin(_ENV[x]) end | 52 | local a = function (x) return math.sin(_ENV[x]) end |
53 | local function f() | ||
54 | return a | ||
55 | end | ||
56 | assert(f() == f()) | ||
57 | end | 57 | end |
58 | assert(f() == f()) | ||
59 | 58 | ||
60 | 59 | ||
61 | -- testing closures with 'for' control variable | 60 | -- testing closures with 'for' control variable |