Skip to content

Commit f1712b1

Browse files
committed
implemented --hard-heap-limit and --heap-target-increment
1 parent 345c6ec commit f1712b1

File tree

7 files changed

+139
-6
lines changed

7 files changed

+139
-6
lines changed

base/options.jl

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

src/gc-mmtk.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ void jl_gc_init(void) {
7878
if (jl_options.heap_size_hint == 0) {
7979
char *cp = getenv(HEAP_SIZE_HINT);
8080
if (cp)
81-
hint = parse_heap_size_hint(cp, "JULIA_HEAP_SIZE_HINT=\"<size>[<unit>]\"");
81+
hint = parse_heap_size_option(cp, "JULIA_HEAP_SIZE_HINT=\"<size>[<unit>]\"");
8282
}
8383
#ifdef _P64
8484
if (hint == 0) {

src/gc-stock.c

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3214,7 +3214,7 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) JL_NOTS
32143214
uint64_t target_heap;
32153215
const char *reason = ""; (void)reason; // for GC_TIME output stats
32163216
old_heap_size = heap_size; // TODO: Update these values dynamically instead of just during the GC
3217-
if (collection == JL_GC_AUTO) {
3217+
if (collection == JL_GC_AUTO && jl_options.hard_heap_limit == 0) {
32183218
// update any heuristics only when the user does not force the GC
32193219
// but still update the timings, since GC was run and reset, even if it was too early
32203220
uint64_t target_allocs = 0.0;
@@ -3295,6 +3295,27 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) JL_NOTS
32953295
target_heap = jl_atomic_load_relaxed(&gc_heap_stats.heap_target);
32963296
}
32973297

3298+
// Kill the process if we are above the hard heap limit
3299+
if (jl_options.hard_heap_limit != 0) {
3300+
if (heap_size > jl_options.hard_heap_limit) {
3301+
// Can't use `jl_errorf` here, because it will try to allocate memory
3302+
// and we are already at the hard limit.
3303+
jl_safe_printf("Heap size exceeded hard limit of %" PRIu64 " bytes.\n",
3304+
jl_options.hard_heap_limit);
3305+
abort();
3306+
}
3307+
}
3308+
// Ignore heap limit computation from MemBalancer-like heuristics
3309+
// if the heap target increment goes above the value specified through
3310+
// `--heap-target-increment`.
3311+
// Note that if we reach this code, we can guarantee that the heap size
3312+
// is less than the hard limit, so there will be some room to grow the heap
3313+
// until the next GC without hitting the hard limit.
3314+
if (jl_options.heap_target_increment != 0) {
3315+
target_heap = heap_size + jl_options.heap_target_increment;
3316+
jl_atomic_store_relaxed(&gc_heap_stats.heap_target, target_heap);
3317+
}
3318+
32983319
double old_ratio = (double)promoted_bytes/(double)heap_size;
32993320
if (heap_size > user_max) {
33003321
next_sweep_full = 1;
@@ -3692,6 +3713,9 @@ void jl_gc_init(void)
36923713
arraylist_new(&finalizer_list_marked, 0);
36933714
arraylist_new(&to_finalize, 0);
36943715
jl_atomic_store_relaxed(&gc_heap_stats.heap_target, default_collect_interval);
3716+
if (jl_options.hard_heap_limit != 0) {
3717+
jl_atomic_store_relaxed(&gc_heap_stats.heap_target, jl_options.hard_heap_limit);
3718+
}
36953719
gc_num.interval = default_collect_interval;
36963720
gc_num.allocd = 0;
36973721
gc_num.max_pause = 0;
@@ -3705,7 +3729,7 @@ void jl_gc_init(void)
37053729
if (jl_options.heap_size_hint == 0) {
37063730
char *cp = getenv(HEAP_SIZE_HINT);
37073731
if (cp)
3708-
hint = parse_heap_size_hint(cp, "JULIA_HEAP_SIZE_HINT=\"<size>[<unit>]\"");
3732+
hint = parse_heap_size_option(cp, "JULIA_HEAP_SIZE_HINT=\"<size>[<unit>]\"");
37093733
}
37103734
#ifdef _P64
37113735
total_mem = uv_get_total_memory();

src/jloptions.c

Lines changed: 31 additions & 2 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)
4041
{
4142
long double value = 0.0;
4243
char unit[4] = {0};
@@ -151,6 +152,8 @@ JL_DLLEXPORT void jl_init_options(void)
151152
0, // strip-ir
152153
0, // permalloc_pkgimg
153154
0, // heap-size-hint
155+
0, // hard-heap-limit
156+
0, // heap-target-increment
154157
0, // trace_compile_timing
155158
JL_TRIM_NO, // trim
156159
0, // task_metrics
@@ -289,6 +292,16 @@ static const char opts[] =
289292
" number of bytes, optionally in units of: B, K (kibibytes),\n"
290293
" M (mebibytes), G (gibibytes), T (tebibytes), or % (percentage\n"
291294
" 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), T (tebibytes), or % (percentage\n"
299+
" of physical memory).\n\n"
300+
" --heap-target-increment=<size>[<unit>] Set an upper bound on how much the heap target\n"
301+
" can increase between consecutive collections. The value may be\n"
302+
" specified as a number of bytes, optionally in units of: B,\n"
303+
" K (kibibytes), M (mebibytes), G (gibibytes), T (tebibytes),\n"
304+
" or % (percentage of physical memory).\n\n"
292305
;
293306

294307
static const char opts_hidden[] =
@@ -380,6 +393,8 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
380393
opt_strip_metadata,
381394
opt_strip_ir,
382395
opt_heap_size_hint,
396+
opt_hard_heap_limit,
397+
opt_heap_target_increment,
383398
opt_gc_threads,
384399
opt_permalloc_pkgimg,
385400
opt_trim,
@@ -451,6 +466,8 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
451466
{ "strip-ir", no_argument, 0, opt_strip_ir },
452467
{ "permalloc-pkgimg",required_argument, 0, opt_permalloc_pkgimg },
453468
{ "heap-size-hint", required_argument, 0, opt_heap_size_hint },
469+
{ "hard-heap-limit", required_argument, 0, opt_hard_heap_limit },
470+
{ "heap-target-increment", required_argument, 0, opt_heap_target_increment },
454471
{ "trim", optional_argument, 0, opt_trim },
455472
{ 0, 0, 0, 0 }
456473
};
@@ -960,11 +977,23 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
960977
break;
961978
case opt_heap_size_hint:
962979
if (optarg != NULL)
963-
jl_options.heap_size_hint = parse_heap_size_hint(optarg, "--heap-size-hint=<size>[<unit>]");
980+
jl_options.heap_size_hint = parse_heap_size_option(optarg, "--heap-size-hint=<size>[<unit>]");
964981
if (jl_options.heap_size_hint == 0)
965982
jl_errorf("julia: invalid memory size specified in --heap-size-hint=<size>[<unit>]");
966983

967984
break;
985+
case opt_hard_heap_limit:
986+
if (optarg != NULL)
987+
jl_options.hard_heap_limit = parse_heap_size_option(optarg, "--hard-heap-limit=<size>[<unit>]");
988+
if (jl_options.hard_heap_limit == 0)
989+
jl_errorf("julia: invalid memory size specified in --hard-heap-limit=<size>[<unit>]");
990+
break;
991+
case opt_heap_target_increment:
992+
if (optarg != NULL)
993+
jl_options.heap_target_increment = parse_heap_size_option(optarg, "--heap-target-increment=<size>[<unit>]");
994+
if (jl_options.heap_target_increment == 0)
995+
jl_errorf("julia: invalid memory size specified in --heap-target-increment=<size>[<unit>]");
996+
break;
968997
case opt_gc_threads:
969998
errno = 0;
970999
long nmarkthreads = strtol(optarg, &endptr, 10);

src/jloptions.h

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

src/julia.h

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

2583-
uint64_t parse_heap_size_hint(const char *optarg, const char *option_name);
2583+
uint64_t parse_heap_size_option(const char *optarg, const char *option_name);
25842584

25852585
// Set julia-level ARGS array according to the arguments provided in
25862586
// argc/argv

test/cmdlineargs.jl

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

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

12191229
## `Main.main` entrypoint
@@ -1253,6 +1263,72 @@ end
12531263
end
12541264
end
12551265

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

0 commit comments

Comments
 (0)