Skip to content

Commit bc23a6d

Browse files
authored
Implement fixed heap size for Julia (#38)
This PR introduces fixed heap size for stock Julia. With the build time option `WITH_GC_FIXED_HEAP=1` and using `--fixed-heap-size=...`, it will bypass all the existing GC triggering heuristics, and only do GC when the heap size reaches the defined heap size, and will only do a full heap GC if the free memory after a GC is less than 20% of the heap size. This PR also introduces a global counter for mallocd bytes. This will slow down the performance of malloc. For MMTK Julia, we also use such a counter (see mmtk/mmtk-julia#141). I plan to do another PR to fix this for both MMTK Julia and stock Julia.
1 parent 48d7e42 commit bc23a6d

File tree

5 files changed

+114
-2
lines changed

5 files changed

+114
-2
lines changed

Make.inc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ HAVE_SSP := 0
8686
WITH_GC_VERIFY := 0
8787
WITH_GC_DEBUG_ENV := 0
8888

89+
# Overwrite Julia's GC heuristics and only trigger a GC if the heap is full (fixed_heap_size needs to be set in this build)
90+
WITH_GC_FIXED_HEAP ?= 0
91+
8992
# MMTk GC
9093
WITH_MMTK ?= 0
9194

@@ -738,6 +741,11 @@ JCXXFLAGS += -DGC_DEBUG_ENV
738741
JCFLAGS += -DGC_DEBUG_ENV
739742
endif
740743

744+
ifeq ($(WITH_GC_FIXED_HEAP), 1)
745+
JCXXFLAGS += -DGC_FIXED_HEAP
746+
JCFLAGS += -DGC_FIXED_HEAP
747+
endif
748+
741749
ifeq ($(WITH_MMTK), 1)
742750
ifeq (${MMTK_JULIA_DIR},)
743751
$(error MMTK_JULIA_DIR must be set to use MMTk)

base/options.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ struct JLOptions
5656
strip_ir::Int8
5757
permalloc_pkgimg::Int8
5858
heap_size_hint::UInt64
59+
fixed_heap_size::UInt64
5960
end
6061

6162
# This runs early in the sysimage != is not defined yet

src/gc.c

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ _Atomic(int) gc_master_tid;
2525
uv_mutex_t gc_threads_lock;
2626
uv_cond_t gc_threads_cond;
2727

28+
#ifdef GC_FIXED_HEAP
29+
// Globally allocated bytes by malloc - used for fixed heap size
30+
_Atomic(uint64_t) malloc_bytes;
31+
// Globally allocated pool pages - used for fixed heap size
32+
extern uint64_t jl_current_pg_count(void);
33+
#endif
34+
2835
// Linked list of callback functions
2936

3037
typedef void (*jl_gc_cb_func_t)(void);
@@ -393,6 +400,9 @@ extern int64_t live_bytes;
393400
static int64_t perm_scanned_bytes; // old bytes scanned while marking
394401
int prev_sweep_full = 1;
395402
int current_sweep_full = 0;
403+
#ifdef GC_FIXED_HEAP
404+
int next_sweep_full = 0; // force next sweep to be a full sweep - used by fixed heap size
405+
#endif
396406

397407
// Full collection heuristics
398408
static int64_t promoted_bytes = 0;
@@ -574,6 +584,18 @@ void gc_setmark_buf(jl_ptls_t ptls, void *o, uint8_t mark_mode, size_t minsz) JL
574584

575585
inline void maybe_collect(jl_ptls_t ptls)
576586
{
587+
#ifdef GC_FIXED_HEAP
588+
if (jl_options.fixed_heap_size) {
589+
uint64_t current_heap_size = ((uint64_t)jl_current_pg_count()) << (uint64_t)14;
590+
current_heap_size += jl_atomic_load_relaxed(&malloc_bytes);
591+
if (current_heap_size >= jl_options.fixed_heap_size) {
592+
jl_gc_collect(JL_GC_AUTO);
593+
} else {
594+
jl_gc_safepoint_(ptls);
595+
}
596+
return;
597+
}
598+
#endif
577599
if (jl_atomic_load_relaxed(&ptls->gc_num.allocd) >= 0 || jl_gc_debug_check_other()) {
578600
jl_gc_collect(JL_GC_AUTO);
579601
}
@@ -2708,6 +2730,16 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection)
27082730
sweep_full = 1;
27092731
recollect = 1;
27102732
}
2733+
#ifdef GC_FIXED_HEAP
2734+
if (jl_options.fixed_heap_size) {
2735+
// For fixed heap size, do not trigger full sweep for any other heuristics
2736+
sweep_full = 0;
2737+
}
2738+
if (next_sweep_full) {
2739+
next_sweep_full = 0;
2740+
sweep_full = 1;
2741+
}
2742+
#endif
27112743
if (sweep_full) {
27122744
// these are the difference between the number of gc-perm bytes scanned
27132745
// on the first collection after sweep_full, and the current scan
@@ -2824,6 +2856,15 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection)
28242856
}
28252857
}
28262858

2859+
#ifdef GC_FIXED_HEAP
2860+
if (jl_options.fixed_heap_size) {
2861+
uint64_t current_heap_size = ((uint64_t)jl_current_pg_count()) << ((uint64_t)14);
2862+
if (current_heap_size > (jl_options.fixed_heap_size * 4 / 5)) {
2863+
next_sweep_full = 1;
2864+
}
2865+
}
2866+
#endif
2867+
28272868
gc_time_summary(sweep_full, t_start, gc_end_time, gc_num.freed,
28282869
live_bytes, gc_num.interval, pause,
28292870
gc_num.time_to_safepoint,
@@ -3029,6 +3070,16 @@ void jl_gc_init(void)
30293070
#endif
30303071
if (jl_options.heap_size_hint)
30313072
jl_gc_set_max_memory(jl_options.heap_size_hint);
3073+
3074+
#ifdef GC_FIXED_HEAP
3075+
if (jl_options.fixed_heap_size) {
3076+
// This guarantees that we will not trigger a GC before reaching heap limit
3077+
gc_num.interval = jl_options.fixed_heap_size;
3078+
} else {
3079+
jl_printf(JL_STDERR, "Warning: The option fixed-heap-size is not set for a build with WITH_GC_FIXED_HEAP\n");
3080+
}
3081+
#endif
3082+
30323083
t_start = jl_hrtime();
30333084
}
30343085

@@ -3045,6 +3096,9 @@ JL_DLLEXPORT void *jl_gc_counted_malloc(size_t sz)
30453096
jl_atomic_load_relaxed(&ptls->gc_num.allocd) + sz);
30463097
jl_atomic_store_relaxed(&ptls->gc_num.malloc,
30473098
jl_atomic_load_relaxed(&ptls->gc_num.malloc) + 1);
3099+
#ifdef GC_FIXED_HEAP
3100+
jl_atomic_fetch_add_relaxed(&malloc_bytes, sz);
3101+
#endif
30483102
}
30493103
return malloc(sz);
30503104
}
@@ -3060,6 +3114,9 @@ JL_DLLEXPORT void *jl_gc_counted_calloc(size_t nm, size_t sz)
30603114
jl_atomic_load_relaxed(&ptls->gc_num.allocd) + nm*sz);
30613115
jl_atomic_store_relaxed(&ptls->gc_num.malloc,
30623116
jl_atomic_load_relaxed(&ptls->gc_num.malloc) + 1);
3117+
#ifdef GC_FIXED_HEAP
3118+
jl_atomic_fetch_add_relaxed(&malloc_bytes, nm * sz);
3119+
#endif
30633120
}
30643121
return calloc(nm, sz);
30653122
}
@@ -3075,6 +3132,9 @@ JL_DLLEXPORT void jl_gc_counted_free_with_size(void *p, size_t sz)
30753132
jl_atomic_load_relaxed(&ptls->gc_num.freed) + sz);
30763133
jl_atomic_store_relaxed(&ptls->gc_num.freecall,
30773134
jl_atomic_load_relaxed(&ptls->gc_num.freecall) + 1);
3135+
#ifdef GC_FIXED_HEAP
3136+
jl_atomic_fetch_add_relaxed(&malloc_bytes, -sz);
3137+
#endif
30783138
}
30793139
}
30803140

@@ -3085,12 +3145,19 @@ JL_DLLEXPORT void *jl_gc_counted_realloc_with_old_size(void *p, size_t old, size
30853145
if (pgcstack != NULL && ct->world_age) {
30863146
jl_ptls_t ptls = ct->ptls;
30873147
maybe_collect(ptls);
3088-
if (sz < old)
3148+
if (sz < old) {
30893149
jl_atomic_store_relaxed(&ptls->gc_num.freed,
30903150
jl_atomic_load_relaxed(&ptls->gc_num.freed) + (old - sz));
3091-
else
3151+
#ifdef GC_FIXED_HEAP
3152+
jl_atomic_fetch_add_relaxed(&malloc_bytes, old - sz);
3153+
#endif
3154+
} else {
30923155
jl_atomic_store_relaxed(&ptls->gc_num.allocd,
30933156
jl_atomic_load_relaxed(&ptls->gc_num.allocd) + (sz - old));
3157+
#ifdef GC_FIXED_HEAP
3158+
jl_atomic_fetch_add_relaxed(&malloc_bytes, sz - old);
3159+
#endif
3160+
}
30943161
jl_atomic_store_relaxed(&ptls->gc_num.realloc,
30953162
jl_atomic_load_relaxed(&ptls->gc_num.realloc) + 1);
30963163
}

src/jloptions.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
255255
opt_strip_metadata,
256256
opt_strip_ir,
257257
opt_heap_size_hint,
258+
opt_fixed_heap_size,
258259
opt_permalloc_pkgimg,
259260
opt_gc_threads,
260261
};
@@ -318,6 +319,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
318319
{ "strip-ir", no_argument, 0, opt_strip_ir },
319320
{ "permalloc-pkgimg",required_argument, 0, opt_permalloc_pkgimg },
320321
{ "heap-size-hint", required_argument, 0, opt_heap_size_hint },
322+
{ "fixed-heap-size", required_argument, 0, opt_fixed_heap_size },
321323
{ 0, 0, 0, 0 }
322324
};
323325

@@ -823,6 +825,39 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
823825
jl_errorf("julia: invalid argument to --heap-size-hint without memory size specified");
824826

825827
break;
828+
case opt_fixed_heap_size:
829+
if (optarg != NULL) {
830+
size_t endof = strlen(optarg);
831+
long double value = 0.0;
832+
if (sscanf(optarg, "%Lf", &value) == 1 && value > 1e-7) {
833+
char unit = optarg[endof - 1];
834+
uint64_t multiplier = 1ull;
835+
switch (unit) {
836+
case 'k':
837+
case 'K':
838+
multiplier <<= 10;
839+
break;
840+
case 'm':
841+
case 'M':
842+
multiplier <<= 20;
843+
break;
844+
case 'g':
845+
case 'G':
846+
multiplier <<= 30;
847+
break;
848+
case 't':
849+
case 'T':
850+
multiplier <<= 40;
851+
break;
852+
default:
853+
break;
854+
}
855+
jl_options.fixed_heap_size = (uint64_t)(value * multiplier);
856+
}
857+
}
858+
if (jl_options.fixed_heap_size == 0)
859+
jl_errorf("julia: invalid argument to --fixed-heap-size without memory size specified");
860+
break;
826861
case opt_permalloc_pkgimg:
827862
if (!strcmp(optarg,"yes"))
828863
jl_options.permalloc_pkgimg = 1;

src/jloptions.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ typedef struct {
6060
int8_t strip_ir;
6161
int8_t permalloc_pkgimg;
6262
uint64_t heap_size_hint;
63+
uint64_t fixed_heap_size;
6364
} jl_options_t;
6465

6566
#endif

0 commit comments

Comments
 (0)