Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement fixed heap size for Julia #38

Merged
merged 6 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Make.inc
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ HAVE_SSP := 0
WITH_GC_VERIFY := 0
WITH_GC_DEBUG_ENV := 0

# 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)
WITH_GC_FIXED_HEAP ?= 0

# MMTk GC
WITH_MMTK ?= 0

Expand Down Expand Up @@ -738,6 +741,11 @@ JCXXFLAGS += -DGC_DEBUG_ENV
JCFLAGS += -DGC_DEBUG_ENV
endif

ifeq ($(WITH_GC_FIXED_HEAP), 1)
JCXXFLAGS += -DGC_FIXED_HEAP
JCFLAGS += -DGC_FIXED_HEAP
endif

ifeq ($(WITH_MMTK), 1)
ifeq (${MMTK_JULIA_DIR},)
$(error MMTK_JULIA_DIR must be set to use MMTk)
Expand Down
1 change: 1 addition & 0 deletions base/options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ struct JLOptions
strip_ir::Int8
permalloc_pkgimg::Int8
heap_size_hint::UInt64
fixed_heap_size::UInt64
end

# This runs early in the sysimage != is not defined yet
Expand Down
71 changes: 69 additions & 2 deletions src/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ _Atomic(int) gc_master_tid;
uv_mutex_t gc_threads_lock;
uv_cond_t gc_threads_cond;

#ifdef GC_FIXED_HEAP
// Globally allocated bytes by malloc - used for fixed heap size
_Atomic(uint64_t) malloc_bytes;
// Globally allocated pool pages - used for fixed heap size
extern uint64_t jl_current_pg_count(void);
#endif

// Linked list of callback functions

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

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

inline void maybe_collect(jl_ptls_t ptls)
{
#ifdef GC_FIXED_HEAP
if (jl_options.fixed_heap_size) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you still need these internal ifs if you have defined GC_FIXED_HEAP? Aren't they redundant?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a build with GC_FIXED_HEAP, you can still run with the stock heuristics if fixed_heap_size is not set. If you think it is a better idea to force using fixed_heap_size, I can change that.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, you're building fixed heap support when you use GC_FIXED_HEAP.
But if you do build with GC_FIXED_HEAP and use the stock heuristics you would pay the performance overhead you mentioned above right? Then you probably want to force using fixed_heap_size when you build with GC_FIXED_HEAP, or even add a warning to make it clear.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a warning. I tried to make it a hard error. But the building process uses julia without setting a heap size. So better also keep the stock heuristics in place.

uint64_t current_heap_size = ((uint64_t)jl_current_pg_count()) << (uint64_t)14;
current_heap_size += jl_atomic_load_relaxed(&malloc_bytes);
if (current_heap_size >= jl_options.fixed_heap_size) {
jl_gc_collect(JL_GC_AUTO);
} else {
jl_gc_safepoint_(ptls);
}
return;
}
#endif
if (jl_atomic_load_relaxed(&ptls->gc_num.allocd) >= 0 || jl_gc_debug_check_other()) {
jl_gc_collect(JL_GC_AUTO);
}
Expand Down Expand Up @@ -2708,6 +2730,16 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection)
sweep_full = 1;
recollect = 1;
}
#ifdef GC_FIXED_HEAP
if (jl_options.fixed_heap_size) {
// For fixed heap size, do not trigger full sweep for any other heuristics
sweep_full = 0;
}
if (next_sweep_full) {
next_sweep_full = 0;
sweep_full = 1;
}
#endif
if (sweep_full) {
// these are the difference between the number of gc-perm bytes scanned
// on the first collection after sweep_full, and the current scan
Expand Down Expand Up @@ -2824,6 +2856,15 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection)
}
}

#ifdef GC_FIXED_HEAP
if (jl_options.fixed_heap_size) {
uint64_t current_heap_size = ((uint64_t)jl_current_pg_count()) << ((uint64_t)14);
if (current_heap_size > (jl_options.fixed_heap_size * 4 / 5)) {
next_sweep_full = 1;
}
}
#endif

gc_time_summary(sweep_full, t_start, gc_end_time, gc_num.freed,
live_bytes, gc_num.interval, pause,
gc_num.time_to_safepoint,
Expand Down Expand Up @@ -3029,6 +3070,16 @@ void jl_gc_init(void)
#endif
if (jl_options.heap_size_hint)
jl_gc_set_max_memory(jl_options.heap_size_hint);

#ifdef GC_FIXED_HEAP
if (jl_options.fixed_heap_size) {
// This guarantees that we will not trigger a GC before reaching heap limit
gc_num.interval = jl_options.fixed_heap_size;
} else {
jl_printf(JL_STDERR, "Warning: The option fixed-heap-size is not set for a build with WITH_GC_FIXED_HEAP\n");
}
#endif

t_start = jl_hrtime();
}

Expand All @@ -3045,6 +3096,9 @@ JL_DLLEXPORT void *jl_gc_counted_malloc(size_t sz)
jl_atomic_load_relaxed(&ptls->gc_num.allocd) + sz);
jl_atomic_store_relaxed(&ptls->gc_num.malloc,
jl_atomic_load_relaxed(&ptls->gc_num.malloc) + 1);
#ifdef GC_FIXED_HEAP
jl_atomic_fetch_add_relaxed(&malloc_bytes, sz);
#endif
}
return malloc(sz);
}
Expand All @@ -3060,6 +3114,9 @@ JL_DLLEXPORT void *jl_gc_counted_calloc(size_t nm, size_t sz)
jl_atomic_load_relaxed(&ptls->gc_num.allocd) + nm*sz);
jl_atomic_store_relaxed(&ptls->gc_num.malloc,
jl_atomic_load_relaxed(&ptls->gc_num.malloc) + 1);
#ifdef GC_FIXED_HEAP
jl_atomic_fetch_add_relaxed(&malloc_bytes, nm * sz);
#endif
}
return calloc(nm, sz);
}
Expand All @@ -3075,6 +3132,9 @@ JL_DLLEXPORT void jl_gc_counted_free_with_size(void *p, size_t sz)
jl_atomic_load_relaxed(&ptls->gc_num.freed) + sz);
jl_atomic_store_relaxed(&ptls->gc_num.freecall,
jl_atomic_load_relaxed(&ptls->gc_num.freecall) + 1);
#ifdef GC_FIXED_HEAP
jl_atomic_fetch_add_relaxed(&malloc_bytes, -sz);
#endif
}
}

Expand All @@ -3085,12 +3145,19 @@ JL_DLLEXPORT void *jl_gc_counted_realloc_with_old_size(void *p, size_t old, size
if (pgcstack != NULL && ct->world_age) {
jl_ptls_t ptls = ct->ptls;
maybe_collect(ptls);
if (sz < old)
if (sz < old) {
jl_atomic_store_relaxed(&ptls->gc_num.freed,
jl_atomic_load_relaxed(&ptls->gc_num.freed) + (old - sz));
else
#ifdef GC_FIXED_HEAP
jl_atomic_fetch_add_relaxed(&malloc_bytes, old - sz);
#endif
} else {
jl_atomic_store_relaxed(&ptls->gc_num.allocd,
jl_atomic_load_relaxed(&ptls->gc_num.allocd) + (sz - old));
#ifdef GC_FIXED_HEAP
jl_atomic_fetch_add_relaxed(&malloc_bytes, sz - old);
#endif
}
jl_atomic_store_relaxed(&ptls->gc_num.realloc,
jl_atomic_load_relaxed(&ptls->gc_num.realloc) + 1);
}
Expand Down
35 changes: 35 additions & 0 deletions src/jloptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
opt_strip_metadata,
opt_strip_ir,
opt_heap_size_hint,
opt_fixed_heap_size,
opt_permalloc_pkgimg,
opt_gc_threads,
};
Expand Down Expand Up @@ -318,6 +319,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
{ "strip-ir", no_argument, 0, opt_strip_ir },
{ "permalloc-pkgimg",required_argument, 0, opt_permalloc_pkgimg },
{ "heap-size-hint", required_argument, 0, opt_heap_size_hint },
{ "fixed-heap-size", required_argument, 0, opt_fixed_heap_size },
{ 0, 0, 0, 0 }
};

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

break;
case opt_fixed_heap_size:
if (optarg != NULL) {
size_t endof = strlen(optarg);
long double value = 0.0;
if (sscanf(optarg, "%Lf", &value) == 1 && value > 1e-7) {
char unit = optarg[endof - 1];
uint64_t multiplier = 1ull;
switch (unit) {
case 'k':
case 'K':
multiplier <<= 10;
break;
case 'm':
case 'M':
multiplier <<= 20;
break;
case 'g':
case 'G':
multiplier <<= 30;
break;
case 't':
case 'T':
multiplier <<= 40;
break;
default:
break;
}
jl_options.fixed_heap_size = (uint64_t)(value * multiplier);
}
}
if (jl_options.fixed_heap_size == 0)
jl_errorf("julia: invalid argument to --fixed-heap-size without memory size specified");
break;
case opt_permalloc_pkgimg:
if (!strcmp(optarg,"yes"))
jl_options.permalloc_pkgimg = 1;
Expand Down
1 change: 1 addition & 0 deletions src/jloptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ typedef struct {
int8_t strip_ir;
int8_t permalloc_pkgimg;
uint64_t heap_size_hint;
uint64_t fixed_heap_size;
} jl_options_t;

#endif