Skip to content

Commit

Permalink
Free objects with finalizer more eagerly
Browse files Browse the repository at this point in the history
* Run all the finalizers at the same time in a dead reference tree
* Reset objects that are only reachable from finalizer list as young and
  clean so that they can be collect during next quick GC without being
  promoted to old gen.

Closes #14127
  • Loading branch information
yuyichao committed Jun 5, 2016
1 parent 9388a82 commit f5ac1f2
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 31 deletions.
14 changes: 8 additions & 6 deletions src/gc-debug.c
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,10 @@ static void gc_verify_track(void)
jl_printf(JL_STDERR, "Now looking for %p =======\n", lostval);
clear_mark(GC_CLEAN);
pre_mark();
gc_mark_object_list(&to_finalize);
post_mark(&finalizer_list, 1);
post_mark(&finalizer_list_marked, 1);
gc_mark_object_list(&to_finalize, 0);
gc_mark_object_list(&finalizer_list, 0);
gc_mark_object_list(&finalizer_list_marked, 0);
visit_mark_stack();
if (lostval_parents.len == 0) {
jl_printf(JL_STDERR, "Could not find the missing link. We missed a toplevel root. This is odd.\n");
break;
Expand Down Expand Up @@ -213,9 +214,10 @@ void gc_verify(void)
clear_mark(GC_CLEAN);
gc_verifying = 1;
pre_mark();
gc_mark_object_list(&to_finalize);
post_mark(&finalizer_list, 1);
post_mark(&finalizer_list_marked, 1);
gc_mark_object_list(&to_finalize, 0);
gc_mark_object_list(&finalizer_list, 0);
gc_mark_object_list(&finalizer_list_marked, 0);
visit_mark_stack();
int clean_len = bits_save[GC_CLEAN].len;
for(int i = 0; i < clean_len + bits_save[GC_QUEUED].len; i++) {
gcval_t *v = (gcval_t*)bits_save[i >= clean_len ? GC_QUEUED : GC_CLEAN].items[i >= clean_len ? i - clean_len : i];
Expand Down
90 changes: 67 additions & 23 deletions src/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,12 @@ static size_t max_collect_interval = 500000000UL;

// global variables for GC stats

// Resetting the object to a young object, this is used when marking the
// finalizer list to collect them the next time because the object is very
// likely dead. This also won't break the GC invariance since these objects
// are not reachable from anywhere else.
static int mark_reset_age = 0;

/*
* The state transition looks like :
*
Expand Down Expand Up @@ -354,12 +360,22 @@ static inline int gc_setmark_big(void *o, int mark_mode)
assert(find_region(o,1) == NULL);
bigval_t *hdr = bigval_header(o);
int bits = gc_bits(o);
if (bits == GC_QUEUED || bits == GC_MARKED)
mark_mode = GC_MARKED;
if ((mark_mode == GC_MARKED) & (bits != GC_MARKED)) {
// Move hdr from big_objects list to big_objects_marked list
if (mark_reset_age && !(bits & GC_MARKED)) {
// Reset the object as if it was just allocated
hdr->age = 0;
gc_big_object_unlink(hdr);
gc_big_object_link(hdr, &big_objects_marked);
gc_big_object_link(hdr, &jl_thread_heap.big_objects);
bits = GC_CLEAN;
mark_mode = GC_MARKED_NOESC;
}
else {
if (bits == GC_QUEUED || bits == GC_MARKED)
mark_mode = GC_MARKED;
if ((mark_mode == GC_MARKED) & (bits != GC_MARKED)) {
// Move hdr from big_objects list to big_objects_marked list
gc_big_object_unlink(hdr);
gc_big_object_link(hdr, &big_objects_marked);
}
}
if (!(bits & GC_MARKED)) {
if (mark_mode == GC_MARKED)
Expand All @@ -385,7 +401,17 @@ static inline int gc_setmark_pool(void *o, int mark_mode)
}
jl_gc_pagemeta_t *page = page_metadata(o);
int bits = gc_bits(o);
if (bits == GC_QUEUED || bits == GC_MARKED) {
if (mark_reset_age && !(bits & GC_MARKED)) {
// Reset the object as if it was just allocated
bits = GC_CLEAN;
mark_mode = GC_MARKED_NOESC;
page->has_young = 1;
char *page_begin = gc_page_data(o) + GC_PAGE_OFFSET;
int obj_id = (((char*)o) - page_begin) / page->osize;
uint8_t *ages = page->ages + obj_id / 8;
*ages &= ~(1 << (obj_id % 8));
}
else if (bits == GC_QUEUED || bits == GC_MARKED) {
mark_mode = GC_MARKED;
}
if (!(bits & GC_MARKED)) {
Expand Down Expand Up @@ -1192,9 +1218,9 @@ NOINLINE static void gc_mark_task(jl_task_t *ta, int d)
gc_mark_task_stack(ta, d);
}

void gc_mark_object_list(arraylist_t *list)
void gc_mark_object_list(arraylist_t *list, size_t start)
{
for (size_t i = 0;i < list->len;i++) {
for (size_t i = start;i < list->len;i++) {
gc_push_root(list->items[i], 0);
}
}
Expand Down Expand Up @@ -1385,7 +1411,7 @@ static int push_root(jl_value_t *v, int d, int bits)
return bits;
}

static void visit_mark_stack(void)
void visit_mark_stack(void)
{
while (mark_sp > 0 && !should_timeout()) {
jl_value_t *v = mark_stack[--mark_sp];
Expand Down Expand Up @@ -1444,16 +1470,16 @@ void pre_mark(void)

// find unmarked objects that need to be finalized from the finalizer list "list".
// this must happen last in the mark phase.
// if dryrun == 1, it does not schedule any actual finalization and only marks finalizers
void post_mark(arraylist_t *list, int dryrun)
static void post_mark(arraylist_t *list)
{
for(size_t i=0; i < list->len; i+=2) {
jl_value_t *v = (jl_value_t*)list->items[i];
jl_value_t *fin = (jl_value_t*)list->items[i+1];
int isfreed = !gc_marked(jl_astaggedvalue(v));
gc_push_root(fin, 0);
int isold = list == &finalizer_list && gc_bits(jl_astaggedvalue(v)) == GC_MARKED && gc_bits(jl_astaggedvalue(fin)) == GC_MARKED;
if (!dryrun && (isfreed || isold)) {
int isold = (list != &finalizer_list_marked &&
gc_bits(jl_astaggedvalue(v)) == GC_MARKED &&
gc_bits(jl_astaggedvalue(fin)) == GC_MARKED);
if (isfreed || isold) {
// remove from this list
if (i < list->len - 2) {
list->items[i] = list->items[list->len-2];
Expand All @@ -1466,19 +1492,19 @@ void post_mark(arraylist_t *list, int dryrun)
// schedule finalizer or execute right away if it is not julia code
if (gc_typeof(fin) == (jl_value_t*)jl_voidpointer_type) {
void *p = jl_unbox_voidpointer(fin);
if (!dryrun && p)
if (p)
((void (*)(void*))p)(jl_data_ptr(v));
continue;
}
gc_push_root(v, 0);
if (!dryrun) schedule_finalization(v, fin);
schedule_finalization(v, fin);
}
if (!dryrun && isold) {
if (isold) {
// The caller relies on the new objects to be pushed to the end of
// the list!!
arraylist_push(&finalizer_list_marked, v);
arraylist_push(&finalizer_list_marked, fin);
}
}
visit_mark_stack();
}

// collector entry point and control
Expand Down Expand Up @@ -1585,10 +1611,28 @@ static void _jl_gc_collect(int full, char *stack_hi)
int64_t actual_allocd = gc_num.since_sweep;
// marking is over
// 4. check for objects to finalize
post_mark(&finalizer_list, 0);
if (prev_sweep_full)
post_mark(&finalizer_list_marked, 0);
gc_mark_object_list(&to_finalize);
// Record the length of the marked list since we need to
// mark the object moved to the marked list from the
// `finalizer_list` by `post_mark`
size_t orig_marked_len = finalizer_list_marked.len;
post_mark(&finalizer_list);
if (prev_sweep_full) {
post_mark(&finalizer_list_marked);
orig_marked_len = 0;
}
gc_mark_object_list(&finalizer_list, 0);
gc_mark_object_list(&finalizer_list_marked, orig_marked_len);
// "Flush" the mark stack before flipping the reset_age bit
// so that the objects are not incorrectly resetted.
visit_mark_stack();
mark_reset_age = 1;
// Reset the age and old bit for any unmarked objects referenced by the
// `to_finalize` list. These objects are only reachable from this list
// and should not be referenced by any old objects so this won't break
// the GC invariant.
gc_mark_object_list(&to_finalize, 0);
visit_mark_stack();
mark_reset_age = 0;
gc_settime_postmark_end();

int64_t live_sz_ub = live_bytes + actual_allocd;
Expand Down
4 changes: 2 additions & 2 deletions src/gc.h
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,8 @@ STATIC_INLINE void gc_big_object_link(bigval_t *hdr, bigval_t **list)
}

void pre_mark(void);
void post_mark(arraylist_t *list, int dryrun);
void gc_mark_object_list(arraylist_t *list);
void gc_mark_object_list(arraylist_t *list, size_t start);
void visit_mark_stack(void);
void gc_debug_init(void);

#define jl_thread_heap (jl_get_ptls_states()->heap)
Expand Down

0 comments on commit f5ac1f2

Please sign in to comment.