Skip to content

Commit 902dcb4

Browse files
gbaraldid-netto
authored andcommitted
Add fallback if we have make a weird GC decision. (JuliaLang#50682)
If something odd happens during GC (the PC goes to sleep) or a very big transient the heuristics might make a bad decision. What this PR implements is if we try to make our target more than double the one we had before we fallback to a more conservative method. This fixes the new issue @vtjnash found in JuliaLang#40644 for me.
1 parent 152eff1 commit 902dcb4

File tree

5 files changed

+76
-27
lines changed

5 files changed

+76
-27
lines changed

Make.inc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,6 +1461,12 @@ endef
14611461
# Overridable in Make.user
14621462
WINE ?= wine
14631463

1464+
ifeq ($(BINARY),32)
1465+
HEAPLIM := --heap-size-hint=1000M
1466+
else
1467+
HEAPLIM :=
1468+
endif
1469+
14641470
# many of the following targets must be = not := because the expansion of the makefile functions (and $1) shouldn't happen until later
14651471
ifeq ($(BUILD_OS), WINNT) # MSYS
14661472
spawn = $(1)

src/gc-debug.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,18 +1223,18 @@ void _report_gc_finished(uint64_t pause, uint64_t freed, int full, int recollect
12231223
if (!gc_logging_enabled) {
12241224
return;
12251225
}
1226-
jl_safe_printf("GC: pause %.2fms. collected %fMB. %s %s\n",
1226+
jl_safe_printf("\nGC: pause %.2fms. collected %fMB. %s %s\n",
12271227
pause/1e6, freed/(double)(1<<20),
12281228
full ? "full" : "incr",
12291229
recollect ? "recollect" : ""
12301230
);
12311231

1232-
jl_safe_printf("Heap stats: bytes_mapped %.2f MB, bytes_resident %.2f MB, heap_size %.2f MB, heap_target %.2f MB, live_bytes %.2f MB\n, Fragmentation %.3f",
1232+
jl_safe_printf("Heap stats: bytes_mapped %.2f MB, bytes_resident %.2f MB,\nheap_size %.2f MB, heap_target %.2f MB, Fragmentation %.3f\n",
12331233
jl_atomic_load_relaxed(&gc_heap_stats.bytes_mapped)/(double)(1<<20),
12341234
jl_atomic_load_relaxed(&gc_heap_stats.bytes_resident)/(double)(1<<20),
1235+
// live_bytes/(double)(1<<20), live byes tracking is not accurate.
12351236
jl_atomic_load_relaxed(&gc_heap_stats.heap_size)/(double)(1<<20),
12361237
jl_atomic_load_relaxed(&gc_heap_stats.heap_target)/(double)(1<<20),
1237-
live_bytes/(double)(1<<20),
12381238
(double)live_bytes/(double)jl_atomic_load_relaxed(&gc_heap_stats.heap_size)
12391239
);
12401240
// Should fragmentation use bytes_resident instead of heap_size?

src/gc.c

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// This file is a part of Julia. License is MIT: https://julialang.org/license
22

33
#include "gc.h"
4+
#include "julia.h"
45
#include "julia_gcext.h"
56
#include "julia_assert.h"
67
#ifdef __GLIBC__
@@ -805,8 +806,8 @@ static uint64_t old_heap_size = 0;
805806
static uint64_t old_alloc_diff = 0;
806807
static uint64_t old_freed_diff = 0;
807808
static uint64_t gc_end_time = 0;
808-
809-
809+
static int thrash_counter = 0;
810+
static int thrashing = 0;
810811
// global variables for GC stats
811812

812813
// Resetting the object to a young object, this is used when marking the
@@ -1277,7 +1278,10 @@ static void combine_thread_gc_counts(jl_gc_num_t *dest) JL_NOTSAFEPOINT
12771278
dest->bigalloc += jl_atomic_load_relaxed(&ptls->gc_num.bigalloc);
12781279
uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc);
12791280
uint64_t free_acc = jl_atomic_load_relaxed(&ptls->gc_num.free_acc);
1281+
dest->freed += jl_atomic_load_relaxed(&ptls->gc_num.free_acc);
12801282
jl_atomic_store_relaxed(&gc_heap_stats.heap_size, alloc_acc - free_acc + jl_atomic_load_relaxed(&gc_heap_stats.heap_size));
1283+
jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0);
1284+
jl_atomic_store_relaxed(&ptls->gc_num.free_acc, 0);
12811285
}
12821286
}
12831287
}
@@ -3363,9 +3367,6 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection)
33633367
// If the live data outgrows the suggested max_total_memory
33643368
// we keep going with minimum intervals and full gcs until
33653369
// we either free some space or get an OOM error.
3366-
if (live_bytes > max_total_memory) {
3367-
sweep_full = 1;
3368-
}
33693370
if (gc_sweep_always_full) {
33703371
sweep_full = 1;
33713372
}
@@ -3408,7 +3409,6 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection)
34083409
gc_num.last_incremental_sweep = gc_end_time;
34093410
}
34103411

3411-
int thrashing = 0; // maybe we should report this to the user or error out?
34123412
size_t heap_size = jl_atomic_load_relaxed(&gc_heap_stats.heap_size);
34133413
double target_allocs = 0.0;
34143414
double min_interval = default_collect_interval;
@@ -3419,24 +3419,32 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection)
34193419
double collect_smooth_factor = 0.5;
34203420
double tuning_factor = 0.03;
34213421
double alloc_mem = jl_gc_smooth(old_alloc_diff, alloc_diff, alloc_smooth_factor);
3422-
double alloc_time = jl_gc_smooth(old_mut_time, mutator_time, alloc_smooth_factor);
3422+
double alloc_time = jl_gc_smooth(old_mut_time, mutator_time + sweep_time, alloc_smooth_factor); // Charge sweeping to the mutator
34233423
double gc_mem = jl_gc_smooth(old_freed_diff, freed_diff, collect_smooth_factor);
3424-
double gc_time = jl_gc_smooth(old_pause_time, pause, collect_smooth_factor);
3424+
double gc_time = jl_gc_smooth(old_pause_time, pause - sweep_time, collect_smooth_factor);
34253425
old_alloc_diff = alloc_diff;
34263426
old_mut_time = mutator_time;
34273427
old_freed_diff = freed_diff;
34283428
old_pause_time = pause;
3429-
old_heap_size = heap_size;
3430-
thrashing = gc_time > mutator_time * 98 ? 1 : 0;
3429+
old_heap_size = heap_size; // TODO: Update these values dynamically instead of just during the GC
3430+
if (gc_time > alloc_time * 95 && !(thrash_counter < 4))
3431+
thrash_counter += 1;
3432+
else if (thrash_counter > 0)
3433+
thrash_counter -= 1;
34313434
if (alloc_mem != 0 && alloc_time != 0 && gc_mem != 0 && gc_time != 0 ) {
34323435
double alloc_rate = alloc_mem/alloc_time;
34333436
double gc_rate = gc_mem/gc_time;
34343437
target_allocs = sqrt(((double)heap_size/min_interval * alloc_rate)/(gc_rate * tuning_factor)); // work on multiples of min interval
34353438
}
34363439
}
3437-
if (target_allocs == 0.0 || thrashing) // If we are thrashing go back to default
3438-
target_allocs = 2*sqrt((double)heap_size/min_interval);
3440+
if (thrashing == 0 && thrash_counter >= 3)
3441+
thrashing = 1;
3442+
else if (thrashing == 1 && thrash_counter <= 2)
3443+
thrashing = 0; // maybe we should report this to the user or error out?
34393444

3445+
int bad_result = (target_allocs*min_interval + heap_size) > 2 * jl_atomic_load_relaxed(&gc_heap_stats.heap_target); // Don't follow through on a bad decision
3446+
if (target_allocs == 0.0 || thrashing || bad_result) // If we are thrashing go back to default
3447+
target_allocs = 2*sqrt((double)heap_size/min_interval);
34403448
uint64_t target_heap = (uint64_t)target_allocs*min_interval + heap_size;
34413449
if (target_heap > max_total_memory && !thrashing) // Allow it to go over if we are thrashing if we die we die
34423450
target_heap = max_total_memory;
@@ -3704,10 +3712,11 @@ void jl_gc_init(void)
37043712
total_mem = uv_get_total_memory();
37053713
uint64_t constrained_mem = uv_get_constrained_memory();
37063714
if (constrained_mem > 0 && constrained_mem < total_mem)
3707-
total_mem = constrained_mem;
3715+
jl_gc_set_max_memory(constrained_mem - 250*1024*1024); // LLVM + other libraries need some amount of memory
37083716
#endif
37093717
if (jl_options.heap_size_hint)
3710-
jl_gc_set_max_memory(jl_options.heap_size_hint);
3718+
jl_gc_set_max_memory(jl_options.heap_size_hint - 250*1024*1024);
3719+
37113720
t_start = jl_hrtime();
37123721
}
37133722

@@ -3808,7 +3817,26 @@ JL_DLLEXPORT void *jl_gc_counted_realloc_with_old_size(void *p, size_t old, size
38083817
jl_atomic_load_relaxed(&ptls->gc_num.allocd) + (sz - old));
38093818
jl_atomic_store_relaxed(&ptls->gc_num.realloc,
38103819
jl_atomic_load_relaxed(&ptls->gc_num.realloc) + 1);
3811-
jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, sz-old);
3820+
3821+
int64_t diff = sz - old;
3822+
if (diff < 0) {
3823+
uint64_t free_acc = jl_atomic_load_relaxed(&ptls->gc_num.free_acc);
3824+
if (free_acc + diff < 16*1024)
3825+
jl_atomic_store_relaxed(&ptls->gc_num.free_acc, free_acc + (-diff));
3826+
else {
3827+
jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, -(free_acc + (-diff)));
3828+
jl_atomic_store_relaxed(&ptls->gc_num.free_acc, 0);
3829+
}
3830+
}
3831+
else {
3832+
uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc);
3833+
if (alloc_acc + diff < 16*1024)
3834+
jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc + diff);
3835+
else {
3836+
jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc + diff);
3837+
jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0);
3838+
}
3839+
}
38123840
}
38133841
return realloc(p, sz);
38143842
}
@@ -3925,7 +3953,27 @@ static void *gc_managed_realloc_(jl_ptls_t ptls, void *d, size_t sz, size_t olds
39253953
jl_atomic_load_relaxed(&ptls->gc_num.allocd) + (allocsz - oldsz));
39263954
jl_atomic_store_relaxed(&ptls->gc_num.realloc,
39273955
jl_atomic_load_relaxed(&ptls->gc_num.realloc) + 1);
3928-
jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, allocsz-oldsz);
3956+
3957+
int64_t diff = allocsz - oldsz;
3958+
if (diff < 0) {
3959+
uint64_t free_acc = jl_atomic_load_relaxed(&ptls->gc_num.free_acc);
3960+
if (free_acc + diff < 16*1024)
3961+
jl_atomic_store_relaxed(&ptls->gc_num.free_acc, free_acc + (-diff));
3962+
else {
3963+
jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, -(free_acc + (-diff)));
3964+
jl_atomic_store_relaxed(&ptls->gc_num.free_acc, 0);
3965+
}
3966+
}
3967+
else {
3968+
uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc);
3969+
if (alloc_acc + diff < 16*1024)
3970+
jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc + diff);
3971+
else {
3972+
jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc + diff);
3973+
jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0);
3974+
}
3975+
}
3976+
39293977
int last_errno = errno;
39303978
#ifdef _OS_WINDOWS_
39313979
DWORD last_error = GetLastError();

test/cmdlineargs.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,6 @@ end
876876
@test lines[3] == "foo"
877877
@test lines[4] == "bar"
878878
end
879-
#heap-size-hint
880-
@test readchomp(`$(Base.julia_cmd()) --startup-file=no --heap-size-hint=500M -e "println(@ccall jl_gc_get_max_memory()::UInt64)"`) == "524288000"
879+
#heap-size-hint, we reserve 250 MB for non GC memory (llvm, etc.)
880+
@test readchomp(`$(Base.julia_cmd()) --startup-file=no --heap-size-hint=500M -e "println(@ccall jl_gc_get_max_memory()::UInt64)"`) == "$((500-250)*1024*1024)"
881881
end

test/testenv.jl

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,7 @@ if !@isdefined(testenv_defined)
3838
function addprocs_with_testenv(X; rr_allowed=true, kwargs...)
3939
exename = rr_allowed ? `$rr_exename $test_exename` : test_exename
4040
if X isa Integer
41-
if Sys.iswindows()
42-
heap_size=round(Int,(Sys.free_memory()/(1024^2)/(X+1)))
43-
heap_size -= 300 # I don't know anymore
44-
else
45-
heap_size=round(Int,(Sys.total_memory()/(1024^2)/(X+1)))
46-
end
41+
heap_size=round(Int,(Sys.free_memory()/(1024^2)/(X+1)))
4742
push!(test_exeflags.exec, "--heap-size-hint=$(heap_size)M")
4843
end
4944
addprocs(X; exename=exename, exeflags=test_exeflags, kwargs...)

0 commit comments

Comments
 (0)