Skip to content

Commit 152eff1

Browse files
oscardssmithd-netto
authored andcommitted
Page based heap size heuristics (JuliaLang#50144)
This PR implements GC heuristics based on the amount of pages allocated instead of live objects like was done before. The heuristic for new heap target is based on https://dl.acm.org/doi/10.1145/3563323 (in summary it argues that the heap target should have square root behaviour). From my testing this fixes JuliaLang#49545 and JuliaLang#49761
1 parent 1dd5dbf commit 152eff1

File tree

9 files changed

+205
-111
lines changed

9 files changed

+205
-111
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Language changes
2424

2525
Compiler/Runtime improvements
2626
-----------------------------
27+
* Updated GC heuristics to count allocated pages instead of individual objects ([#50144]).
2728

2829
* Time to first execution (TTFX, sometimes called time to first plot) is greatly reduced. Package precompilation now
2930
saves native code into a "pkgimage", meaning that code generated during the precompilation process will not

doc/src/devdocs/gc.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ This scheme eliminates the need of explicitly keeping a flag to indicate a full
6666
## Heuristics
6767

6868
GC heuristics tune the GC by changing the size of the allocation interval between garbage collections.
69-
If a GC was unproductive, then we increase the size of the allocation interval to allow objects more time to die.
70-
If a GC returns a lot of space we can shrink the interval. The goal is to find a steady state where we are
71-
allocating just about the same amount as we are collecting.
69+
70+
The GC heuristics measure how big the heap size is after a collection and set the next
71+
collection according to the algorithm described by https://dl.acm.org/doi/10.1145/3563323,
72+
in summary, it argues that the heap target should have a square root relationship with the live heap, and that it should also be scaled by how fast the GC is freeing objects and how fast the mutators are allocating.
73+
The heuristics measure the heap size by counting the number of pages that are in use and the objects that use malloc. Previously we measured the heap size by counting
74+
the alive objects, but that doesn't take into account fragmentation which could lead to bad decisions, that also meant that we used thread local information (allocations) to make
75+
decisions about a process wide (when to GC), measuring pages means the decision is global.
76+
77+
The GC will do full collections when the heap size reaches 80% of the maximum allowed size.

src/gc-debug.c

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
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 <inttypes.h>
6+
#include <stddef.h>
7+
#include <stdint.h>
58
#include <stdio.h>
69

710
// re-include assert.h without NDEBUG,
@@ -1216,15 +1219,25 @@ JL_DLLEXPORT void jl_enable_gc_logging(int enable) {
12161219
gc_logging_enabled = enable;
12171220
}
12181221

1219-
void _report_gc_finished(uint64_t pause, uint64_t freed, int full, int recollect) JL_NOTSAFEPOINT {
1222+
void _report_gc_finished(uint64_t pause, uint64_t freed, int full, int recollect, int64_t live_bytes) JL_NOTSAFEPOINT {
12201223
if (!gc_logging_enabled) {
12211224
return;
12221225
}
12231226
jl_safe_printf("GC: pause %.2fms. collected %fMB. %s %s\n",
1224-
pause/1e6, freed/1e6,
1227+
pause/1e6, freed/(double)(1<<20),
12251228
full ? "full" : "incr",
12261229
recollect ? "recollect" : ""
12271230
);
1231+
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",
1233+
jl_atomic_load_relaxed(&gc_heap_stats.bytes_mapped)/(double)(1<<20),
1234+
jl_atomic_load_relaxed(&gc_heap_stats.bytes_resident)/(double)(1<<20),
1235+
jl_atomic_load_relaxed(&gc_heap_stats.heap_size)/(double)(1<<20),
1236+
jl_atomic_load_relaxed(&gc_heap_stats.heap_target)/(double)(1<<20),
1237+
live_bytes/(double)(1<<20),
1238+
(double)live_bytes/(double)jl_atomic_load_relaxed(&gc_heap_stats.heap_size)
1239+
);
1240+
// Should fragmentation use bytes_resident instead of heap_size?
12281241
}
12291242

12301243
#ifdef __cplusplus

src/gc-pages.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ char *jl_gc_try_alloc_pages_(int pg_cnt) JL_NOTSAFEPOINT
8383
// round data pointer up to the nearest gc_page_data-aligned
8484
// boundary if mmap didn't already do so.
8585
mem = (char*)gc_page_data(mem + GC_PAGE_SZ - 1);
86+
jl_atomic_fetch_add_relaxed(&gc_heap_stats.bytes_mapped, pages_sz);
87+
jl_atomic_fetch_add_relaxed(&gc_heap_stats.bytes_resident, pages_sz);
8688
return mem;
8789
}
8890

@@ -138,6 +140,7 @@ NOINLINE jl_gc_pagemeta_t *jl_gc_alloc_page(void) JL_NOTSAFEPOINT
138140
// try to get page from `pool_freed`
139141
meta = pop_lf_back(&global_page_pool_freed);
140142
if (meta != NULL) {
143+
jl_atomic_fetch_add_relaxed(&gc_heap_stats.bytes_resident, GC_PAGE_SZ);
141144
gc_alloc_map_set(meta->data, GC_PAGE_ALLOCATED);
142145
goto exit;
143146
}
@@ -213,6 +216,7 @@ void jl_gc_free_page(jl_gc_pagemeta_t *pg) JL_NOTSAFEPOINT
213216
#endif
214217
msan_unpoison(p, decommit_size);
215218
jl_atomic_fetch_add(&current_pg_count, -1);
219+
jl_atomic_fetch_add_relaxed(&gc_heap_stats.bytes_resident, -decommit_size);
216220
}
217221

218222
#ifdef __cplusplus

0 commit comments

Comments
 (0)