Skip to content

Commit 93e353e

Browse files
committed
py/gc: Implement GC running by allocation threshold.
Currently, MicroPython runs GC when it could not allocate a block of memory, which happens when heap is exhausted. However, that policy can't work well with "inifinity" heaps, e.g. backed by a virtual memory - there will be a lot of swap thrashing long before VM will be exhausted. Instead, in such cases "allocation threshold" policy is used: a GC is run after some number of allocations have been made. Details vary, for example, number or total amount of allocations can be used, threshold may be self-adjusting based on GC outcome, etc. This change implements a simple variant of such policy for MicroPython. Amount of allocated memory so far is used for threshold, to make it useful to typical finite-size, and small, heaps as used with MicroPython ports. And such GC policy is indeed useful for such types of heaps too, as it allows to better control fragmentation. For example, if a threshold is set to half size of heap, then for an application which usually makes big number of small allocations, that will (try to) keep half of heap memory in a nice defragmented state for an occasional large allocation. For an application which doesn't exhibit such behavior, there won't be any visible effects, except for GC running more frequently, which however may affect performance. To address this, the GC threshold is configurable, and by default is off so far. It's configured with gc.threshold(amount_in_bytes) call (can be queries without an argument).
1 parent 04c27e5 commit 93e353e

4 files changed

Lines changed: 55 additions & 0 deletions

File tree

py/gc.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ void gc_init(void *start, void *end) {
152152
// allow auto collection
153153
MP_STATE_MEM(gc_auto_collect_enabled) = 1;
154154

155+
#if MICROPY_GC_ALLOC_THRESHOLD
156+
// by default, maxuint for gc threshold, effectively turning gc-by-threshold off
157+
MP_STATE_MEM(gc_alloc_threshold) = (size_t)-1;
158+
MP_STATE_MEM(gc_alloc_amount) = 0;
159+
#endif
160+
155161
#if MICROPY_PY_THREAD
156162
mp_thread_mutex_init(&MP_STATE_MEM(gc_mutex));
157163
#endif
@@ -294,6 +300,9 @@ STATIC void gc_sweep(void) {
294300
void gc_collect_start(void) {
295301
GC_ENTER();
296302
MP_STATE_MEM(gc_lock_depth)++;
303+
#if MICROPY_GC_ALLOC_THRESHOLD
304+
MP_STATE_MEM(gc_alloc_amount) = 0;
305+
#endif
297306
MP_STATE_MEM(gc_stack_overflow) = 0;
298307
MP_STATE_MEM(gc_sp) = MP_STATE_MEM(gc_stack);
299308
// Trace root pointers. This relies on the root pointers being organised
@@ -405,6 +414,15 @@ void *gc_alloc(size_t n_bytes, bool has_finaliser) {
405414
size_t start_block;
406415
size_t n_free = 0;
407416
int collected = !MP_STATE_MEM(gc_auto_collect_enabled);
417+
418+
#if MICROPY_GC_ALLOC_THRESHOLD
419+
if (!collected && MP_STATE_MEM(gc_alloc_amount) >= MP_STATE_MEM(gc_alloc_threshold)) {
420+
GC_EXIT();
421+
gc_collect();
422+
GC_ENTER();
423+
}
424+
#endif
425+
408426
for (;;) {
409427

410428
// look for a run of n_blocks available blocks
@@ -456,6 +474,10 @@ void *gc_alloc(size_t n_bytes, bool has_finaliser) {
456474
void *ret_ptr = (void*)(MP_STATE_MEM(gc_pool_start) + start_block * BYTES_PER_BLOCK);
457475
DEBUG_printf("gc_alloc(%p)\n", ret_ptr);
458476

477+
#if MICROPY_GC_ALLOC_THRESHOLD
478+
MP_STATE_MEM(gc_alloc_amount) += n_blocks;
479+
#endif
480+
459481
GC_EXIT();
460482

461483
// zero out the additional bytes of the newly allocated blocks

py/modgc.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,25 @@ STATIC mp_obj_t gc_mem_alloc(void) {
8383
}
8484
MP_DEFINE_CONST_FUN_OBJ_0(gc_mem_alloc_obj, gc_mem_alloc);
8585

86+
#if MICROPY_GC_ALLOC_THRESHOLD
87+
STATIC mp_obj_t gc_threshold(size_t n_args, const mp_obj_t *args) {
88+
if (n_args == 0) {
89+
if (MP_STATE_MEM(gc_alloc_threshold) == (size_t)-1) {
90+
return MP_OBJ_NEW_SMALL_INT(-1);
91+
}
92+
return mp_obj_new_int(MP_STATE_MEM(gc_alloc_threshold) * MICROPY_BYTES_PER_GC_BLOCK);
93+
}
94+
mp_int_t val = mp_obj_get_int(args[0]);
95+
if (val < 0) {
96+
MP_STATE_MEM(gc_alloc_threshold) = (size_t)-1;
97+
} else {
98+
MP_STATE_MEM(gc_alloc_threshold) = val / MICROPY_BYTES_PER_GC_BLOCK;
99+
}
100+
return mp_const_none;
101+
}
102+
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(gc_threshold_obj, 0, 1, gc_threshold);
103+
#endif
104+
86105
STATIC const mp_rom_map_elem_t mp_module_gc_globals_table[] = {
87106
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_gc) },
88107
{ MP_ROM_QSTR(MP_QSTR_collect), MP_ROM_PTR(&gc_collect_obj) },
@@ -91,6 +110,9 @@ STATIC const mp_rom_map_elem_t mp_module_gc_globals_table[] = {
91110
{ MP_ROM_QSTR(MP_QSTR_isenabled), MP_ROM_PTR(&gc_isenabled_obj) },
92111
{ MP_ROM_QSTR(MP_QSTR_mem_free), MP_ROM_PTR(&gc_mem_free_obj) },
93112
{ MP_ROM_QSTR(MP_QSTR_mem_alloc), MP_ROM_PTR(&gc_mem_alloc_obj) },
113+
#if MICROPY_GC_ALLOC_THRESHOLD
114+
{ MP_ROM_QSTR(MP_QSTR_threshold), MP_ROM_PTR(&gc_threshold_obj) },
115+
#endif
94116
};
95117

96118
STATIC MP_DEFINE_CONST_DICT(mp_module_gc_globals, mp_module_gc_globals_table);

py/mpconfig.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@
107107
#define MICROPY_ALLOC_GC_STACK_SIZE (64)
108108
#endif
109109

110+
// Support automatic GC when reaching allocation threshold,
111+
// configurable by gc.threshold().
112+
#ifndef MICROPY_GC_ALLOC_THRESHOLD
113+
#define MICROPY_GC_ALLOC_THRESHOLD (1)
114+
#endif
115+
110116
// Number of bytes to allocate initially when creating new chunks to store
111117
// interned string data. Smaller numbers lead to more chunks being needed
112118
// and more wastage at the end of the chunk. Larger numbers lead to wasted

py/mpstate.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ typedef struct _mp_state_mem_t {
7676
// you can still allocate/free memory and also explicitly call gc_collect.
7777
uint16_t gc_auto_collect_enabled;
7878

79+
#if MICROPY_GC_ALLOC_THRESHOLD
80+
size_t gc_alloc_amount;
81+
size_t gc_alloc_threshold;
82+
#endif
83+
7984
size_t gc_last_free_atb_index;
8085

8186
#if MICROPY_PY_GC_COLLECT_RETVAL

0 commit comments

Comments
 (0)