Skip to content

Commit 15bdcbc

Browse files
committed
Rewrite GC page metadata for sweeping fast path
* Delete `gc_bits` and `allocd` which are not enough to accurately record the necessary information. * Add `has_marked` and `has_young` to identify free pages and untouched pages. Fixes #15543
1 parent 35e4a29 commit 15bdcbc

File tree

3 files changed

+52
-31
lines changed

3 files changed

+52
-31
lines changed

src/gc-debug.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,8 @@ static void gc_scrub_range(char *stack_lo, char *stack_hi)
348348
continue;
349349
jl_gc_pagemeta_t *pg = page_metadata(tag);
350350
// Make sure the sweep rebuild the freelist
351-
pg->allocd = 1;
352-
pg->gc_bits = 0x3;
351+
pg->has_marked = 1;
352+
pg->has_young = 1;
353353
// Find the age bit
354354
char *page_begin = gc_page_data(tag) + GC_PAGE_OFFSET;
355355
int obj_id = (((char*)tag) - page_begin) / osize;
@@ -358,6 +358,7 @@ static void gc_scrub_range(char *stack_lo, char *stack_hi)
358358
// (especially on 32bit where it's more likely to have pointer-like
359359
// bit patterns)
360360
*ages &= ~(1 << (obj_id % 8));
361+
// set mark to GC_MARKED_NOESC (young and marked)
361362
memset(tag, 0xff, osize);
362363
}
363364
}

src/gc.c

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,6 @@ static int64_t scanned_bytes; // young bytes scanned while marking
320320
static int64_t perm_scanned_bytes; // old bytes scanned while marking
321321
static int prev_sweep_mask = GC_MARKED;
322322

323-
static size_t array_nbytes(jl_array_t*);
324323
#define inc_sat(v,s) v = (v) >= s ? s : (v)+1
325324

326325
// Full collection heuristics
@@ -415,8 +414,9 @@ static inline int gc_setmark_pool(void *o, int mark_mode)
415414
objprofile_count(jl_typeof(jl_valueof(o)),
416415
mark_mode == GC_MARKED, page->osize);
417416
}
417+
assert(mark_mode & GC_MARKED);
418+
page->has_marked = 1;
418419
_gc_setmark(o, mark_mode);
419-
page->gc_bits |= mark_mode;
420420
verify_val(jl_valueof(o));
421421
return mark_mode;
422422
}
@@ -667,15 +667,15 @@ static void sweep_malloced_arrays(void)
667667
// pool allocation
668668
static inline gcval_t *reset_page(jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, gcval_t *fl)
669669
{
670-
pg->gc_bits = 0;
671670
pg->nfree = (GC_PAGE_SZ - GC_PAGE_OFFSET) / p->osize;
672671
jl_tls_states_t *ptls = jl_all_tls_states[pg->thread_n];
673672
pg->pool_n = p - ptls->heap.norm_pools;
674673
memset(pg->ages, 0, GC_PAGE_SZ / 8 / p->osize + 1);
675674
gcval_t *beg = (gcval_t*)(pg->data + GC_PAGE_OFFSET);
676675
gcval_t *end = (gcval_t*)((char*)beg + (pg->nfree - 1)*p->osize);
677676
end->next = fl;
678-
pg->allocd = 0;
677+
pg->has_young = 0;
678+
pg->has_marked = 0;
679679
pg->fl_begin_offset = GC_PAGE_OFFSET;
680680
pg->fl_end_offset = (char*)end - (char*)beg + GC_PAGE_OFFSET;
681681
return beg;
@@ -724,7 +724,7 @@ static inline void *__pool_alloc(jl_gc_pool_t *p, int osize, int end_offset)
724724
jl_gc_pagemeta_t *pg = page_metadata(v);
725725
assert(pg->osize == p->osize);
726726
pg->nfree = 0;
727-
pg->allocd = 1;
727+
pg->has_young = 1;
728728
if (next)
729729
p->nfree = page_metadata(next)->nfree;
730730
}
@@ -745,7 +745,7 @@ static inline void *__pool_alloc(jl_gc_pool_t *p, int osize, int end_offset)
745745
jl_gc_pagemeta_t *pg = page_metadata(v);
746746
assert(pg->osize == p->osize);
747747
pg->nfree = 0;
748-
pg->allocd = 1;
748+
pg->has_young = 1;
749749
p->newpages = v->next;
750750
}
751751
v->flags = 0;
@@ -873,12 +873,15 @@ static gcval_t **sweep_page(jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, gcval_t **pfl
873873
freedall = 1;
874874
old_nfree += pg->nfree;
875875

876-
if (pg->gc_bits == GC_MARKED) {
877-
// this page only contains GC_MARKED and free cells
878-
// if we are doing a quick sweep and nothing has been allocated inside since last sweep
879-
// we can skip it
880-
if (sweep_mask == GC_MARKED_NOESC && !pg->allocd) {
881-
// the position of the freelist begin/end in this page is stored in its metadata
876+
if (!pg->has_marked)
877+
goto free_page;
878+
// For quick sweep, we might be able to skip the page if the page doesn't
879+
// have any young live cell before marking.
880+
if (sweep_mask == GC_MARKED_NOESC && !pg->has_young) {
881+
// TODO handle `prev_sweep_mask == GC_MARKED` with additional counters
882+
if (prev_sweep_mask == GC_MARKED_NOESC) {
883+
// the position of the freelist begin/end in this page
884+
// is stored in its metadata
882885
if (pg->fl_begin_offset != (uint16_t)-1) {
883886
*pfl = page_pfl_beg(pg);
884887
pfl = prev_pfl = (gcval_t**)page_pfl_end(pg);
@@ -888,11 +891,10 @@ static gcval_t **sweep_page(jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, gcval_t **pfl
888891
goto free_page;
889892
}
890893
}
891-
else if (pg->gc_bits == GC_CLEAN) {
892-
goto free_page;
893-
}
894894

895895
{ // scope to avoid clang goto errors
896+
int has_marked = 0;
897+
int has_young = 0;
896898
int pg_nfree = 0;
897899
gcval_t **pfl_begin = NULL;
898900
uint8_t msk = 1; // mask for the age bit in the current age byte
@@ -906,14 +908,19 @@ static gcval_t **sweep_page(jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, gcval_t **pfl
906908
*ages &= ~msk;
907909
}
908910
else { // marked young or old
909-
if (*ages & msk) { // old enough
911+
if (*ages & msk || bits == GC_MARKED) { // old enough
912+
// `!age && bits == GC_MARKED` is possible for
913+
// non-first-class objects like `jl_binding_t`
910914
if (sweep_mask == GC_MARKED || bits == GC_MARKED_NOESC) {
911-
gc_bits(v) = GC_QUEUED; // promote
915+
bits = gc_bits(v) = GC_QUEUED; // promote
912916
}
913917
}
914-
else if ((sweep_mask & bits) == sweep_mask) {
915-
gc_bits(v) = GC_CLEAN; // unmark
918+
else {
919+
assert(bits == GC_MARKED_NOESC);
920+
bits = gc_bits(v) = GC_CLEAN; // unmark
921+
has_young = 1;
916922
}
923+
has_marked |= (bits & GC_MARKED) != 0;
917924
*ages |= msk;
918925
freedall = 0;
919926
}
@@ -925,12 +932,14 @@ static gcval_t **sweep_page(jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, gcval_t **pfl
925932
}
926933
}
927934

935+
assert(!freedall);
936+
pg->has_marked = has_marked;
937+
pg->has_young = has_young;
928938
pg->fl_begin_offset = pfl_begin ? (char*)pfl_begin - data : (uint16_t)-1;
929939
pg->fl_end_offset = pfl_begin ? (char*)pfl - data : (uint16_t)-1;
930940

931941
pg->nfree = pg_nfree;
932942
page_done++;
933-
pg->allocd = 0;
934943
}
935944
free_page:
936945
pg_freedall += freedall;
@@ -966,10 +975,6 @@ static gcval_t **sweep_page(jl_gc_pool_t *p, jl_gc_pagemeta_t *pg, gcval_t **pfl
966975
nfree += obj_per_page;
967976
}
968977
else {
969-
if (sweep_mask == GC_MARKED)
970-
pg->gc_bits = GC_CLEAN;
971-
if (sweep_mask == GC_MARKED_NOESC)
972-
pg->gc_bits = GC_MARKED;
973978
nfree += pg->nfree;
974979
}
975980

@@ -1024,7 +1029,7 @@ static int gc_sweep_inc(int sweep_mask)
10241029
gcval_t *last = p->freelist;
10251030
if (last) {
10261031
jl_gc_pagemeta_t *pg = page_metadata(last);
1027-
pg->allocd = 1;
1032+
pg->has_young = 1;
10281033
pg->nfree = p->nfree;
10291034
}
10301035
p->freelist = NULL;
@@ -1034,7 +1039,7 @@ static int gc_sweep_inc(int sweep_mask)
10341039
if (last) {
10351040
jl_gc_pagemeta_t *pg = page_metadata(last);
10361041
pg->nfree = (GC_PAGE_SZ - ((char*)last - gc_page_data(last))) / p->osize;
1037-
pg->allocd = 1;
1042+
pg->has_young = 1;
10381043
}
10391044
p->newpages = NULL;
10401045
}

src/gc.h

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,24 @@ typedef struct _mallocarray_t {
138138
// pool page metadata
139139
typedef struct {
140140
struct {
141-
uint16_t pool_n : 8; // index (into norm_pool) of pool that owns this page
142-
uint16_t allocd : 1; // true if an allocation happened in this page since last sweep
143-
uint16_t gc_bits : 2; // this is a bitwise | of all gc_bits in this page
141+
// index of pool that owns this page
142+
uint16_t pool_n : 8;
143+
// Whether any cell in the page is marked
144+
// This bit is set before sweeping iff there's live cells in the page.
145+
// Note that before marking or after sweeping there can be live
146+
// (and young) cells in the page for `!has_marked`.
147+
uint16_t has_marked: 1;
148+
// Whether any cell was live and young **before sweeping**.
149+
// For a normal sweep (quick sweep that is NOT preceded by a
150+
// full sweep) this bit is set iff there are young or newly dead
151+
// objects in the page and the page needs to be swept.
152+
//
153+
// For a full sweep, this bit should be ignored.
154+
//
155+
// For a quick sweep preceded by a full sweep. If this bit is set,
156+
// the page needs to be swept. If this bit is not set, there could
157+
// still be old dead objects in the page.
158+
uint16_t has_young: 1;
144159
};
145160
uint16_t nfree; // number of free objects in this page.
146161
// invalid if pool that owns this page is allocating objects from this page.

0 commit comments

Comments
 (0)