Skip to content

Commit e5bfe36

Browse files
d-nettoqinsoon
authored andcommitted
Introduce a few GC controls to limit the heap size when running benchmarks (JuliaLang#58487)
We will benefit from having more control over Julia's heap size when benchmarking MMTk: This PR introduces two heap-limit flags: - `--hard-heap-limit`: Set a hard limit on the heap size: if we ever go above this limit, we will abort. - `--upper-bound-for-heap-target-increment`: Set an upper bound on how much the heap target can increase between consecutive collections. Note that they are behind a `GC_ENABLE_HIDDEN_CTRLS` build-time flag, so these options won't be available for most Julia users. It may be a bit tricky to test this, given that the flags are only enabled if you define `GC_ENABLE_HIDDEN_CTRLS`.
1 parent 2744a2a commit e5bfe36

File tree

7 files changed

+147
-14
lines changed

7 files changed

+147
-14
lines changed

base/options.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ struct JLOptions
5959
strip_ir::Int8
6060
permalloc_pkgimg::Int8
6161
heap_size_hint::UInt64
62+
hard_heap_limit::UInt64
63+
heap_target_increment::UInt64
6264
trace_compile_timing::Int8
6365
trim::Int8
6466
task_metrics::Int8

src/gc-mmtk.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ void jl_gc_init(void) {
8585
if (jl_options.heap_size_hint == 0) {
8686
char *cp = getenv(HEAP_SIZE_HINT);
8787
if (cp)
88-
hint = parse_heap_size_hint(cp, "JULIA_HEAP_SIZE_HINT=\"<size>[<unit>]\"");
88+
hint = parse_heap_size_option(cp, "JULIA_HEAP_SIZE_HINT=\"<size>[<unit>]\"", 1);
8989
}
9090
#ifdef _P64
9191
if (hint == 0) {

src/gc-stock.c

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3221,7 +3221,7 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection)
32213221
uint64_t target_heap;
32223222
const char *reason = ""; (void)reason; // for GC_TIME output stats
32233223
old_heap_size = heap_size; // TODO: Update these values dynamically instead of just during the GC
3224-
if (collection == JL_GC_AUTO) {
3224+
if (collection == JL_GC_AUTO && jl_options.hard_heap_limit == 0) {
32253225
// update any heuristics only when the user does not force the GC
32263226
// but still update the timings, since GC was run and reset, even if it was too early
32273227
uint64_t target_allocs = 0.0;
@@ -3302,6 +3302,27 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection)
33023302
target_heap = jl_atomic_load_relaxed(&gc_heap_stats.heap_target);
33033303
}
33043304

3305+
// Kill the process if we are above the hard heap limit
3306+
if (jl_options.hard_heap_limit != 0) {
3307+
if (heap_size > jl_options.hard_heap_limit) {
3308+
// Can't use `jl_errorf` here, because it will try to allocate memory
3309+
// and we are already at the hard limit.
3310+
jl_safe_printf("Heap size exceeded hard limit of %" PRIu64 " bytes.\n",
3311+
jl_options.hard_heap_limit);
3312+
abort();
3313+
}
3314+
}
3315+
// Ignore heap limit computation from MemBalancer-like heuristics
3316+
// if the heap target increment goes above the value specified through
3317+
// `--heap-target-increment`.
3318+
// Note that if we reach this code, we can guarantee that the heap size
3319+
// is less than the hard limit, so there will be some room to grow the heap
3320+
// until the next GC without hitting the hard limit.
3321+
if (jl_options.heap_target_increment != 0) {
3322+
target_heap = heap_size + jl_options.heap_target_increment;
3323+
jl_atomic_store_relaxed(&gc_heap_stats.heap_target, target_heap);
3324+
}
3325+
33053326
double old_ratio = (double)promoted_bytes/(double)heap_size;
33063327
if (heap_size > user_max) {
33073328
next_sweep_full = 1;
@@ -3710,6 +3731,9 @@ void jl_gc_init(void)
37103731
arraylist_new(&finalizer_list_marked, 0);
37113732
arraylist_new(&to_finalize, 0);
37123733
jl_atomic_store_relaxed(&gc_heap_stats.heap_target, default_collect_interval);
3734+
if (jl_options.hard_heap_limit != 0) {
3735+
jl_atomic_store_relaxed(&gc_heap_stats.heap_target, jl_options.hard_heap_limit);
3736+
}
37133737
gc_num.interval = default_collect_interval;
37143738
gc_num.allocd = 0;
37153739
gc_num.max_pause = 0;
@@ -3723,7 +3747,7 @@ void jl_gc_init(void)
37233747
if (jl_options.heap_size_hint == 0) {
37243748
char *cp = getenv(HEAP_SIZE_HINT);
37253749
if (cp)
3726-
hint = parse_heap_size_hint(cp, "JULIA_HEAP_SIZE_HINT=\"<size>[<unit>]\"");
3750+
hint = parse_heap_size_option(cp, "JULIA_HEAP_SIZE_HINT=\"<size>[<unit>]\"", 1);
37273751
}
37283752
#ifdef _P64
37293753
total_mem = uv_get_total_memory();

src/jloptions.c

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <limits.h>
44
#include <errno.h>
55

6+
#include "options.h"
67
#include "julia.h"
78
#include "julia_internal.h"
89

@@ -36,7 +37,7 @@ JL_DLLEXPORT const char *jl_get_default_sysimg_path(void)
3637

3738
/* This function is also used by gc-stock.c to parse the
3839
* JULIA_HEAP_SIZE_HINT environment variable. */
39-
uint64_t parse_heap_size_hint(const char *optarg, const char *option_name)
40+
uint64_t parse_heap_size_option(const char *optarg, const char *option_name, int allow_pct)
4041
{
4142
long double value = 0.0;
4243
char unit[4] = {0};
@@ -62,14 +63,16 @@ uint64_t parse_heap_size_hint(const char *optarg, const char *option_name)
6263
multiplier <<= 40;
6364
break;
6465
case '%':
65-
if (value > 100)
66-
jl_errorf("julia: invalid percentage specified in %s", option_name);
67-
uint64_t mem = uv_get_total_memory();
68-
uint64_t cmem = uv_get_constrained_memory();
69-
if (cmem > 0 && cmem < mem)
70-
mem = cmem;
71-
multiplier = mem/100;
72-
break;
66+
if (allow_pct) {
67+
if (value > 100)
68+
jl_errorf("julia: invalid percentage specified in %s", option_name);
69+
uint64_t mem = uv_get_total_memory();
70+
uint64_t cmem = uv_get_constrained_memory();
71+
if (cmem > 0 && cmem < mem)
72+
mem = cmem;
73+
multiplier = mem/100;
74+
break;
75+
}
7376
default:
7477
jl_errorf("julia: invalid argument to %s (%s)", option_name, optarg);
7578
break;
@@ -150,6 +153,8 @@ JL_DLLEXPORT void jl_init_options(void)
150153
0, // strip-ir
151154
0, // permalloc_pkgimg
152155
0, // heap-size-hint
156+
0, // hard-heap-limit
157+
0, // heap-target-increment
153158
0, // trace_compile_timing
154159
JL_TRIM_NO, // trim
155160
0, // task_metrics
@@ -287,6 +292,14 @@ static const char opts[] =
287292
" number of bytes, optionally in units of: B, K (kibibytes),\n"
288293
" M (mebibytes), G (gibibytes), T (tebibytes), or % (percentage\n"
289294
" of physical memory).\n\n"
295+
" --hard-heap-limit=<size>[<unit>] Set a hard limit on the heap size: if we ever go above this\n"
296+
" limit, we will abort. The value may be specified as a\n"
297+
" number of bytes, optionally in units of: B, K (kibibytes),\n"
298+
" M (mebibytes), G (gibibytes) or T (tebibytes).\n\n"
299+
" --heap-target-increment=<size>[<unit>] Set an upper bound on how much the heap target\n"
300+
" can increase between consecutive collections. The value may be\n"
301+
" specified as a number of bytes, optionally in units of: B,\n"
302+
" K (kibibytes), M (mebibytes), G (gibibytes) or T (tebibytes).\n\n"
290303
;
291304

292305
static const char opts_hidden[] =
@@ -378,6 +391,8 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
378391
opt_strip_metadata,
379392
opt_strip_ir,
380393
opt_heap_size_hint,
394+
opt_hard_heap_limit,
395+
opt_heap_target_increment,
381396
opt_gc_threads,
382397
opt_permalloc_pkgimg,
383398
opt_trim,
@@ -449,6 +464,8 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
449464
{ "strip-ir", no_argument, 0, opt_strip_ir },
450465
{ "permalloc-pkgimg",required_argument, 0, opt_permalloc_pkgimg },
451466
{ "heap-size-hint", required_argument, 0, opt_heap_size_hint },
467+
{ "hard-heap-limit", required_argument, 0, opt_hard_heap_limit },
468+
{ "heap-target-increment", required_argument, 0, opt_heap_target_increment },
452469
{ "trim", optional_argument, 0, opt_trim },
453470
{ 0, 0, 0, 0 }
454471
};
@@ -951,11 +968,23 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
951968
break;
952969
case opt_heap_size_hint:
953970
if (optarg != NULL)
954-
jl_options.heap_size_hint = parse_heap_size_hint(optarg, "--heap-size-hint=<size>[<unit>]");
971+
jl_options.heap_size_hint = parse_heap_size_option(optarg, "--heap-size-hint=<size>[<unit>]", 1);
955972
if (jl_options.heap_size_hint == 0)
956973
jl_errorf("julia: invalid memory size specified in --heap-size-hint=<size>[<unit>]");
957974

958975
break;
976+
case opt_hard_heap_limit:
977+
if (optarg != NULL)
978+
jl_options.hard_heap_limit = parse_heap_size_option(optarg, "--hard-heap-limit=<size>[<unit>]", 0);
979+
if (jl_options.hard_heap_limit == 0)
980+
jl_errorf("julia: invalid memory size specified in --hard-heap-limit=<size>[<unit>]");
981+
break;
982+
case opt_heap_target_increment:
983+
if (optarg != NULL)
984+
jl_options.heap_target_increment = parse_heap_size_option(optarg, "--heap-target-increment=<size>[<unit>]", 0);
985+
if (jl_options.heap_target_increment == 0)
986+
jl_errorf("julia: invalid memory size specified in --heap-target-increment=<size>[<unit>]");
987+
break;
959988
case opt_gc_threads:
960989
errno = 0;
961990
long nmarkthreads = strtol(optarg, &endptr, 10);

src/jloptions.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ typedef struct {
6363
int8_t strip_ir;
6464
int8_t permalloc_pkgimg;
6565
uint64_t heap_size_hint;
66+
uint64_t hard_heap_limit;
67+
uint64_t heap_target_increment;
6668
int8_t trace_compile_timing;
6769
int8_t trim;
6870
int8_t task_metrics;

src/julia.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2696,7 +2696,7 @@ JL_DLLEXPORT ssize_t jl_sizeof_jl_options(void);
26962696
JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp);
26972697
JL_DLLEXPORT char *jl_format_filename(const char *output_pattern);
26982698

2699-
uint64_t parse_heap_size_hint(const char *optarg, const char *option_name);
2699+
uint64_t parse_heap_size_option(const char *optarg, const char *option_name, int allow_pct);
27002700

27012701
// Set julia-level ARGS array according to the arguments provided in
27022702
// argc/argv

test/cmdlineargs.jl

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,16 @@ end
12171217

12181218
@test readchomp(`$(Base.julia_cmd()) --startup-file=no --heap-size-hint=10M -e "println(@ccall jl_gc_get_max_memory()::UInt64)"`) == "$(1*1024*1024)"
12191219
end
1220+
1221+
@testset "hard heap limit" begin
1222+
# Set the hard heap limit to 100MB, try to allocate an array of 200MB
1223+
# and assert that the process is aborted, by checking the exit code.
1224+
cmd = `$(Base.julia_cmd()) --startup-file=no --hard-heap-limit=100M -e "a = Array{UInt8}(undef, 200*1024*1024); GC.gc()"`
1225+
p = open(pipeline(cmd, stderr=devnull, stdout=devnull))
1226+
exitcode = wait(p)
1227+
# The process should be aborted with an error code
1228+
@test exitcode != 0
1229+
end
12201230
end
12211231

12221232
## `Main.main` entrypoint
@@ -1256,6 +1266,72 @@ end
12561266
end
12571267
end
12581268

1269+
@testset "--hard-heap-limit" begin
1270+
exename = `$(Base.julia_cmd())`
1271+
@test errors_not_signals(`$exename --hard-heap-limit -e "exit(0)"`)
1272+
@testset "--hard-heap-limit=$str" for str in ["asdf","","0","1.2vb","b","GB","2.5GB̂","1.2gb2","42gigabytes","5gig","2GiB","NaNt"]
1273+
@test errors_not_signals(`$exename --hard-heap-limit=$str -e "exit(0)"`)
1274+
end
1275+
k = 1024
1276+
m = 1024k
1277+
g = 1024m
1278+
t = 1024g
1279+
# Express one hundred megabytes as 100MB, 100m, 100e6, etc.
1280+
one_hundred_mb_strs_and_vals = [
1281+
("100000000", 100000000), ("1e8", 1e8), ("100MB", 100m), ("100m", 100m), ("1e5kB", 1e5k),
1282+
]
1283+
@testset "--hard-heap-limit=$str" for (str, val) in one_hundred_mb_strs_and_vals
1284+
@test parse(UInt64,read(`$exename --hard-heap-limit=$str -E "Base.JLOptions().hard_heap_limit"`, String)) == val
1285+
end
1286+
# Express two and a half gigabytes as 2.5g, 2.5GB, etc.
1287+
two_and_a_half_gigabytes_strs_and_vals = [
1288+
("2500000000", 2500000000), ("2.5e9", 2.5e9), ("2.5g", 2.5g), ("2.5GB", 2.5g), ("2.5e6mB", 2.5e6m),
1289+
]
1290+
@testset "--hard-heap-limit=$str" for (str, val) in two_and_a_half_gigabytes_strs_and_vals
1291+
@test parse(UInt64,read(`$exename --hard-heap-limit=$str -E "Base.JLOptions().hard_heap_limit"`, String)) == val
1292+
end
1293+
# Express one terabyte as 1TB, 1e12, etc.
1294+
one_terabyte_strs_and_vals = [
1295+
("1000000000000", 1000000000000), ("1e12", 1e12), ("1TB", 1t), ("1e9gB", 1e9g),
1296+
]
1297+
@testset "--hard-heap-limit=$str" for (str, val) in one_terabyte_strs_and_vals
1298+
@test parse(UInt64,read(`$exename --hard-heap-limit=$str -E "Base.JLOptions().hard_heap_limit"`, String)) == val
1299+
end
1300+
end
1301+
1302+
@testset "--heap-target-increment" begin
1303+
exename = `$(Base.julia_cmd())`
1304+
@test errors_not_signals(`$exename --heap-target-increment -e "exit(0)"`)
1305+
@testset "--heap-target-increment=$str" for str in ["asdf","","0","1.2vb","b","GB","2.5GB̂","1.2gb2","42gigabytes","5gig","2GiB","NaNt"]
1306+
@test errors_not_signals(`$exename --heap-target-increment=$str -e "exit(0)"`)
1307+
end
1308+
k = 1024
1309+
m = 1024k
1310+
g = 1024m
1311+
t = 1024g
1312+
# Express one hundred megabytes as 100MB, 100m, 100e6, etc.
1313+
one_hundred_mb_strs_and_vals = [
1314+
("100000000", 100000000), ("1e8", 1e8), ("100MB", 100m), ("100m", 100m), ("1e5kB", 1e5k),
1315+
]
1316+
@testset "--heap-target-increment=$str" for (str, val) in one_hundred_mb_strs_and_vals
1317+
@test parse(UInt64,read(`$exename --heap-target-increment=$str -E "Base.JLOptions().heap_target_increment"`, String)) == val
1318+
end
1319+
# Express two and a half gigabytes as 2.5g, 2.5GB, etc.
1320+
two_and_a_half_gigabytes_strs_and_vals = [
1321+
("2500000000", 2500000000), ("2.5e9", 2.5e9), ("2.5g", 2.5g), ("2.5GB", 2.5g), ("2.5e6mB", 2.5e6m),
1322+
]
1323+
@testset "--heap-target-increment=$str" for (str, val) in two_and_a_half_gigabytes_strs_and_vals
1324+
@test parse(UInt64,read(`$exename --heap-target-increment=$str -E "Base.JLOptions().heap_target_increment"`, String)) == val
1325+
end
1326+
# Express one terabyte as 1TB, 1e12, etc.
1327+
one_terabyte_strs_and_vals = [
1328+
("1000000000000", 1000000000000), ("1e12", 1e12), ("1TB", 1t), ("1e9gB", 1e9g),
1329+
]
1330+
@testset "--heap-target-increment=$str" for (str, val) in one_terabyte_strs_and_vals
1331+
@test parse(UInt64,read(`$exename --heap-target-increment=$str -E "Base.JLOptions().heap_target_increment"`, String)) == val
1332+
end
1333+
end
1334+
12591335
@testset "--timeout-for-safepoint-straggler" begin
12601336
exename = `$(Base.julia_cmd())`
12611337
timeout = 120

0 commit comments

Comments
 (0)