diff options
| author | Angus Gratton <angus@redyak.com.au> | 2023-08-02 16:49:44 +1000 |
|---|---|---|
| committer | Damien George <damien@micropython.org> | 2023-08-15 10:48:02 +1000 |
| commit | 519c24dd48764f28593112dbeac7e30b8b2a6240 (patch) | |
| tree | 9ca0435f8687854e29fc34cd92824089f2213c94 /py/gc.c | |
| parent | d325ee4509d8f68e341d5efc5ddbe3c30f86089e (diff) | |
py/gc: Add new MICROPY_GC_SPLIT_HEAP_AUTO "auto grow heap" mode.
When set, the split heap is automatically extended with new areas on
demand, and shrunk if a heap area becomes empty during a GC pass or soft
reset.
To save code size the size allocation for a new heap block (including
metadata) is estimated at 103% of the failed allocation, rather than
working from the more complex algorithm in gc_try_add_heap(). This appears
to work well except in the extreme limit case when almost all RAM is
exhausted (~last few hundred bytes). However in this case some allocation
is likely to fail soon anyhow.
Currently there is no API to manually add a block of a given size to the
heap, although that could easily be added if necessary.
This work was funded through GitHub Sponsors.
Signed-off-by: Angus Gratton <angus@redyak.com.au>
Diffstat (limited to 'py/gc.c')
| -rw-r--r-- | py/gc.c | 107 |
1 files changed, 105 insertions, 2 deletions
@@ -236,6 +236,83 @@ void gc_add(void *start, void *end) { // Add this area to the linked list prev_area->next = area; } + +#if MICROPY_GC_SPLIT_HEAP_AUTO +// Try to automatically add a heap area large enough to fulfill 'failed_alloc'. +STATIC bool gc_try_add_heap(size_t failed_alloc) { + // 'needed' is the size of a heap large enough to hold failed_alloc, with + // the additional metadata overheads as calculated in gc_setup_area(). + // + // Rather than reproduce all of that logic here, we approximate that adding + // (13/512) is enough overhead for sufficiently large heap areas (the + // overhead converges to 3/128, but there's some fixed overhead and some + // rounding up of partial block sizes). + size_t needed = failed_alloc + MAX(2048, failed_alloc * 13 / 512); + + size_t avail = gc_get_max_new_split(); + + DEBUG_printf("gc_try_add_heap failed_alloc " UINT_FMT ", " + "needed " UINT_FMT ", avail " UINT_FMT " bytes \n", + failed_alloc, + needed, + avail); + + if (avail < needed) { + // Can't fit this allocation, or system heap has nearly run out anyway + return false; + } + + // Deciding how much to grow the total heap by each time is tricky: + // + // - Grow by too small amounts, leads to heap fragmentation issues. + // + // - Grow by too large amounts, may lead to system heap running out of + // space. + // + // Currently, this implementation is: + // + // - At minimum, aim to double the total heap size each time we add a new + // heap. i.e. without any large single allocations, total size will be + // 64KB -> 128KB -> 256KB -> 512KB -> 1MB, etc + // + // - If the failed allocation is too large to fit in that size, the new + // heap is made exactly large enough for that allocation. Future growth + // will double the total heap size again. + // + // - If the new heap won't fit in the available free space, add the largest + // new heap that will fit (this may lead to failed system heap allocations + // elsewhere, but some allocation will likely fail in this circumstance!) + size_t total_heap = 0; + for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); + area != NULL; + area = NEXT_AREA(area)) { + total_heap += area->gc_pool_end - area->gc_alloc_table_start; + total_heap += ALLOC_TABLE_GAP_BYTE + sizeof(mp_state_mem_area_t); + } + + DEBUG_printf("total_heap " UINT_FMT " bytes\n", total_heap); + + size_t to_alloc = MIN(avail, MAX(total_heap, needed)); + + mp_state_mem_area_t *new_heap = MP_PLAT_ALLOC_HEAP(to_alloc); + + DEBUG_printf("MP_PLAT_ALLOC_HEAP " UINT_FMT " = %p\n", + to_alloc, new_heap); + + if (new_heap == NULL) { + // This should only fail: + // - In a threaded environment if another thread has + // allocated while this function ran. + // - If there is a bug in gc_get_max_new_split(). + return false; + } + + gc_add(new_heap, (void *)new_heap + to_alloc); + + return true; +} +#endif + #endif void gc_lock(void) { @@ -392,6 +469,9 @@ STATIC void gc_sweep(void) { #endif // free unmarked heads and their tails int free_tail = 0; + #if MICROPY_GC_SPLIT_HEAP_AUTO + mp_state_mem_area_t *prev_area = NULL; + #endif for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) { size_t end_block = area->gc_alloc_table_byte_len * BLOCKS_PER_ATB; if (area->gc_last_used_block < end_block) { @@ -454,6 +534,17 @@ STATIC void gc_sweep(void) { } area->gc_last_used_block = last_used_block; + + #if MICROPY_GC_SPLIT_HEAP_AUTO + // Free any empty area, aside from the first one + if (last_used_block == 0 && prev_area != NULL) { + DEBUG_printf("gc_sweep free empty area %p\n", area); + NEXT_AREA(prev_area) = NEXT_AREA(area); + MP_PLAT_FREE_HEAP(area); + area = prev_area; + } + prev_area = area; + #endif } } @@ -636,6 +727,9 @@ void *gc_alloc(size_t n_bytes, unsigned int alloc_flags) { size_t start_block; size_t n_free; int collected = !MP_STATE_MEM(gc_auto_collect_enabled); + #if MICROPY_GC_SPLIT_HEAP_AUTO + bool added = false; + #endif #if MICROPY_GC_ALLOC_THRESHOLD if (!collected && MP_STATE_MEM(gc_alloc_amount) >= MP_STATE_MEM(gc_alloc_threshold)) { @@ -681,6 +775,12 @@ void *gc_alloc(size_t n_bytes, unsigned int alloc_flags) { GC_EXIT(); // nothing found! if (collected) { + #if MICROPY_GC_SPLIT_HEAP_AUTO + if (!added && gc_try_add_heap(n_bytes)) { + added = true; + continue; + } + #endif return NULL; } DEBUG_printf("gc_alloc(" UINT_FMT "): no free mem, triggering GC\n", n_bytes); @@ -1056,9 +1156,12 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) { void gc_dump_info(const mp_print_t *print) { gc_info_t info; gc_info(&info); - mp_printf(print, "GC: total: %u, used: %u, free: %u\n", + mp_printf(print, "GC: total: %u, used: %u, free: %u", (uint)info.total, (uint)info.used, (uint)info.free); - mp_printf(print, " No. of 1-blocks: %u, 2-blocks: %u, max blk sz: %u, max free sz: %u\n", + #if MICROPY_GC_SPLIT_HEAP_AUTO + mp_printf(print, ", max new split: %u", (uint)gc_get_max_new_split()); + #endif + mp_printf(print, "\n No. of 1-blocks: %u, 2-blocks: %u, max blk sz: %u, max free sz: %u\n", (uint)info.num_1block, (uint)info.num_2block, (uint)info.max_block, (uint)info.max_free); } |
