Skip to content

Commit 4b0de2d

Browse files
author
Christine H. Flood
committed
Updated GC Heuristics to avoid a full GC unless absolutely necessary.
This helps with JuliaLang#40644 and other programs which suffer from non-productive full collections.
1 parent dc0aa61 commit 4b0de2d

File tree

4 files changed

+66
-32
lines changed

4 files changed

+66
-32
lines changed

src/gc-debug.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,15 @@ void gc_time_sweep_pause(uint64_t gc_end_t, int64_t actual_allocd,
977977
jl_ns2ms(gc_postmark_end - gc_premark_end),
978978
sweep_full ? "full" : "quick", -gc_num.allocd / 1024);
979979
}
980+
981+
void gc_time_summary(int sweep_full, uint64_t start, uint64_t end, uint64_t freed, uint64_t live, uint64_t interval, uint64_t pause){
982+
if (sweep_full > 0)
983+
jl_safe_printf("%ld Major collection: estimate freed = %ld live = %ldm new interval = %ldm time = %ldms\n",
984+
end - start, freed, live/1024/1024, interval/1024/1024, pause/1000000 );
985+
else
986+
jl_safe_printf("%ld Minor collection: estimate freed = %ld live = %ldm new interval = %ldm time = %ldms\n",
987+
end - start, freed, live/1024/1024, interval/1024/1024, pause/1000000 );}
988+
980989
#endif
981990

982991
void jl_gc_debug_init(void)

src/gc.c

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ static jl_gc_callback_list_t *gc_cblist_post_gc;
2727
static jl_gc_callback_list_t *gc_cblist_notify_external_alloc;
2828
static jl_gc_callback_list_t *gc_cblist_notify_external_free;
2929

30+
static uint64_t t_start = 0;
31+
3032
#define gc_invoke_callbacks(ty, list, args) \
3133
do { \
3234
for (jl_gc_callback_list_t *cb = list; \
@@ -1847,14 +1849,19 @@ STATIC_INLINE int gc_mark_scan_objarray(jl_ptls_t ptls, jl_gc_mark_sp_t *sp,
18471849
jl_value_t **begin, jl_value_t **end,
18481850
jl_value_t **pnew_obj, uintptr_t *ptag, uint8_t *pbits)
18491851
{
1852+
1853+
18501854
(void)jl_assume(objary == (gc_mark_objarray_t*)sp->data);
18511855
for (; begin < end; begin += objary->step) {
18521856
*pnew_obj = *begin;
18531857
if (*pnew_obj)
18541858
verify_parent2("obj array", objary->parent, begin, "elem(%d)",
18551859
gc_slot_to_arrayidx(objary->parent, begin));
1856-
if (!gc_try_setmark(*pnew_obj, &objary->nptr, ptag, pbits))
1857-
continue;
1860+
1861+
if (!gc_try_setmark(*pnew_obj, &objary->nptr, ptag, pbits)) {
1862+
continue;
1863+
}
1864+
18581865
begin += objary->step;
18591866
// Found an object to mark
18601867
if (begin < end) {
@@ -2547,7 +2554,7 @@ mark: {
25472554
jl_value_t **data = jl_svec_data(new_obj);
25482555
size_t dtsz = l * sizeof(void*) + sizeof(jl_svec_t);
25492556
if (update_meta)
2550-
gc_setmark(ptls, o, bits, dtsz);
2557+
gc_setmark(ptls, o, bits, dtsz);
25512558
else if (foreign_alloc)
25522559
objprofile_count(vt, bits == GC_OLD_MARKED, dtsz);
25532560
uintptr_t nptr = (l << 2) | (bits & GC_OLD);
@@ -2984,6 +2991,7 @@ static void jl_gc_premark(jl_ptls_t ptls2)
29842991
ptls2->heap.remset->len = 0;
29852992
ptls2->heap.remset_nptr = 0;
29862993

2994+
29872995
// avoid counting remembered objects & bindings twice
29882996
// in `perm_scanned_bytes`
29892997
size_t len = remset->len;
@@ -3125,6 +3133,7 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection)
31253133
int64_t live_sz_est = scanned_bytes + perm_scanned_bytes;
31263134
int64_t estimate_freed = live_sz_ub - live_sz_est;
31273135

3136+
31283137
gc_verify(ptls);
31293138

31303139
gc_stats_all_pool();
@@ -3134,50 +3143,48 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection)
31343143
gc_num.total_allocd += gc_num.since_sweep;
31353144
if (!prev_sweep_full)
31363145
promoted_bytes += perm_scanned_bytes - last_perm_scanned_bytes;
3146+
31373147
// 5. next collection decision
31383148
int not_freed_enough = (collection == JL_GC_AUTO) && estimate_freed < (7*(actual_allocd/10));
3149+
31393150
int nptr = 0;
31403151
for (int i = 0;i < jl_n_threads;i++)
31413152
nptr += jl_all_tls_states[i]->heap.remset_nptr;
3142-
int large_frontier = nptr*sizeof(void*) >= default_collect_interval; // many pointers in the intergen frontier => "quick" mark is not quick
3153+
3154+
int large_frontier = nptr*sizeof(void*) >= default_collect_interval;
3155+
3156+
// many pointers in the intergen frontier => "quick" mark is not quick
31433157
// trigger a full collection if the number of live bytes doubles since the last full
31443158
// collection and then remains at least that high for a while.
31453159
if (grown_heap_age == 0) {
3146-
if (live_bytes > 2 * last_full_live)
3147-
grown_heap_age = 1;
3160+
if (live_bytes > 2 * last_full_live)
3161+
grown_heap_age = 1;
31483162
}
31493163
else if (live_bytes >= last_live_bytes) {
31503164
grown_heap_age++;
31513165
}
3166+
31523167
int sweep_full = 0;
31533168
int recollect = 0;
3154-
if ((large_frontier ||
3155-
((not_freed_enough || promoted_bytes >= gc_num.interval) &&
3156-
(promoted_bytes >= default_collect_interval || prev_sweep_full)) ||
3157-
grown_heap_age > 1) && gc_num.pause > 1) {
3158-
sweep_full = 1;
3169+
3170+
if (not_freed_enough) {
3171+
gc_num.interval = gc_num.interval * 2;
31593172
}
3160-
// update heuristics only if this GC was automatically triggered
3161-
if (collection == JL_GC_AUTO) {
3162-
if (sweep_full) {
3163-
if (large_frontier)
3164-
gc_num.interval = last_long_collect_interval;
3165-
if (not_freed_enough || large_frontier) {
3166-
if (gc_num.interval <= 2*(max_collect_interval/5)) {
3167-
gc_num.interval = 5 * (gc_num.interval / 2);
3168-
}
3169-
}
3170-
last_long_collect_interval = gc_num.interval;
3171-
}
3172-
else {
3173-
// reset interval to default, or at least half of live_bytes
3174-
int64_t half = live_bytes/2;
3175-
if (default_collect_interval < half && half <= max_collect_interval)
3176-
gc_num.interval = half;
3177-
else
3178-
gc_num.interval = default_collect_interval;
3179-
}
3173+
3174+
if (large_frontier) {
3175+
sweep_full = 1;
3176+
}
3177+
3178+
if (gc_num.interval > max_collect_interval) {
3179+
3180+
if (not_freed_enough)
3181+
sweep_full = 1;
3182+
3183+
gc_num.interval = max_collect_interval;
31803184
}
3185+
3186+
3187+
// update heuristics only if this GC was automatically triggered
31813188
if (gc_sweep_always_full) {
31823189
sweep_full = 1;
31833190
}
@@ -3248,10 +3255,20 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection)
32483255
gc_num.allocd = 0;
32493256
last_live_bytes = live_bytes;
32503257
live_bytes += -gc_num.freed + gc_num.since_sweep;
3258+
3259+
// If the current interval is larger than half the live data decrease the interval
3260+
int64_t half = live_bytes/2;
3261+
if (gc_num.interval > half) gc_num.interval = half;
3262+
// But never go below default
3263+
if (gc_num.interval < default_collect_interval) gc_num.interval = default_collect_interval;
3264+
3265+
gc_time_summary(sweep_full, t_start, gc_end_t, gc_num.freed, live_bytes, gc_num.interval, pause);
3266+
32513267
if (prev_sweep_full) {
32523268
last_full_live = live_bytes;
32533269
grown_heap_age = 0;
32543270
}
3271+
32553272
prev_sweep_full = sweep_full;
32563273
gc_num.pause += !recollect;
32573274
gc_num.total_time += pause;
@@ -3390,6 +3407,7 @@ void jl_init_thread_heap(jl_ptls_t ptls)
33903407
jl_atomic_store_relaxed(&ptls->gc_num.allocd, -(int64_t)gc_num.interval);
33913408
}
33923409

3410+
33933411
// System-wide initializations
33943412
void jl_gc_init(void)
33953413
{
@@ -3406,6 +3424,7 @@ void jl_gc_init(void)
34063424
gc_num.interval = default_collect_interval;
34073425
last_long_collect_interval = default_collect_interval;
34083426
gc_num.allocd = 0;
3427+
t_start = jl_hrtime();
34093428

34103429
#ifdef _P64
34113430
// on a big memory machine, set max_collect_interval to totalmem / ncores / 2
@@ -3419,6 +3438,7 @@ void jl_gc_init(void)
34193438
#endif
34203439
jl_gc_mark_sp_t sp = {NULL, NULL, NULL, NULL};
34213440
gc_mark_loop(NULL, sp);
3441+
t_start = jl_hrtime();
34223442
}
34233443

34243444
// callback for passing OOM errors from gmp

src/gc.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,9 @@ void gc_time_mark_pause(int64_t t0, int64_t scanned_bytes,
557557
void gc_time_sweep_pause(uint64_t gc_end_t, int64_t actual_allocd,
558558
int64_t live_bytes, int64_t estimate_freed,
559559
int sweep_full);
560+
void gc_time_summary(int sweep_full, uint64_t start, uint64_t end,
561+
uint64_t freed, uint64_t live, uint64_t interval,
562+
uint64_t pause);
560563
#else
561564
#define gc_time_pool_start()
562565
STATIC_INLINE void gc_time_count_page(int freedall, int pg_skpd) JL_NOTSAFEPOINT
@@ -582,6 +585,8 @@ STATIC_INLINE void gc_time_count_mallocd_array(int bits) JL_NOTSAFEPOINT
582585
#define gc_time_mark_pause(t0, scanned_bytes, perm_scanned_bytes)
583586
#define gc_time_sweep_pause(gc_end_t, actual_allocd, live_bytes, \
584587
estimate_freed, sweep_full)
588+
#define gc_time_summary(sweep_full, start, end, freed, live, \
589+
interval, pause)
585590
#endif
586591

587592
#ifdef MEMFENCE

src/options.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
//#define MEMPROFILE
7979

8080
// GC_TIME prints time taken by each phase of GC
81-
// #define GC_TIME
81+
//#define GC_TIME
8282

8383
// OBJPROFILE counts objects by type
8484
// #define OBJPROFILE

0 commit comments

Comments
 (0)