|
79 | 79 | #define ATB_3_IS_FREE(a) (((a) & ATB_MASK_3) == 0)
|
80 | 80 |
|
81 | 81 | #if MICROPY_GC_SPLIT_HEAP
|
82 |
| -#define NEXT_AREA(area) (area->next) |
| 82 | +#define NEXT_AREA(area) ((area)->next) |
83 | 83 | #else
|
84 | 84 | #define NEXT_AREA(area) (NULL)
|
85 | 85 | #endif
|
@@ -129,7 +129,13 @@ STATIC void gc_setup_area(mp_state_mem_area_t *area, void *start, void *end) {
|
129 | 129 | // => T = A * (1 + BLOCKS_PER_ATB / BLOCKS_PER_FTB + BLOCKS_PER_ATB * BYTES_PER_BLOCK)
|
130 | 130 | size_t total_byte_len = (byte *)end - (byte *)start;
|
131 | 131 | #if MICROPY_ENABLE_FINALISER
|
132 |
| - area->gc_alloc_table_byte_len = (total_byte_len - ALLOC_TABLE_GAP_BYTE) * MP_BITS_PER_BYTE / (MP_BITS_PER_BYTE + MP_BITS_PER_BYTE * BLOCKS_PER_ATB / BLOCKS_PER_FTB + MP_BITS_PER_BYTE * BLOCKS_PER_ATB * BYTES_PER_BLOCK); |
| 132 | + area->gc_alloc_table_byte_len = (total_byte_len - ALLOC_TABLE_GAP_BYTE) |
| 133 | + * MP_BITS_PER_BYTE |
| 134 | + / ( |
| 135 | + MP_BITS_PER_BYTE |
| 136 | + + MP_BITS_PER_BYTE * BLOCKS_PER_ATB / BLOCKS_PER_FTB |
| 137 | + + MP_BITS_PER_BYTE * BLOCKS_PER_ATB * BYTES_PER_BLOCK |
| 138 | + ); |
133 | 139 | #else
|
134 | 140 | area->gc_alloc_table_byte_len = (total_byte_len - ALLOC_TABLE_GAP_BYTE) / (1 + MP_BITS_PER_BYTE / 2 * BYTES_PER_BLOCK);
|
135 | 141 | #endif
|
@@ -164,11 +170,19 @@ STATIC void gc_setup_area(mp_state_mem_area_t *area, void *start, void *end) {
|
164 | 170 | #endif
|
165 | 171 |
|
166 | 172 | DEBUG_printf("GC layout:\n");
|
167 |
| - DEBUG_printf(" alloc table at %p, length " UINT_FMT " bytes, " UINT_FMT " blocks\n", MP_STATE_MEM(area).gc_alloc_table_start, MP_STATE_MEM(area).gc_alloc_table_byte_len, MP_STATE_MEM(area).gc_alloc_table_byte_len * BLOCKS_PER_ATB); |
| 173 | + DEBUG_printf(" alloc table at %p, length " UINT_FMT " bytes, " |
| 174 | + UINT_FMT " blocks\n", |
| 175 | + area->gc_alloc_table_start, area->gc_alloc_table_byte_len, |
| 176 | + area->gc_alloc_table_byte_len * BLOCKS_PER_ATB); |
168 | 177 | #if MICROPY_ENABLE_FINALISER
|
169 |
| - DEBUG_printf(" finaliser table at %p, length " UINT_FMT " bytes, " UINT_FMT " blocks\n", MP_STATE_MEM(area).gc_finaliser_table_start, gc_finaliser_table_byte_len, gc_finaliser_table_byte_len * BLOCKS_PER_FTB); |
| 178 | + DEBUG_printf(" finaliser table at %p, length " UINT_FMT " bytes, " |
| 179 | + UINT_FMT " blocks\n", area->gc_finaliser_table_start, |
| 180 | + gc_finaliser_table_byte_len, |
| 181 | + gc_finaliser_table_byte_len * BLOCKS_PER_FTB); |
170 | 182 | #endif
|
171 |
| - DEBUG_printf(" pool at %p, length " UINT_FMT " bytes, " UINT_FMT " blocks\n", MP_STATE_MEM(area).gc_pool_start, gc_pool_block_len * BYTES_PER_BLOCK, gc_pool_block_len); |
| 183 | + DEBUG_printf(" pool at %p, length " UINT_FMT " bytes, " |
| 184 | + UINT_FMT " blocks\n", area->gc_pool_start, |
| 185 | + gc_pool_block_len * BYTES_PER_BLOCK, gc_pool_block_len); |
172 | 186 | }
|
173 | 187 |
|
174 | 188 | void gc_init(void *start, void *end) {
|
@@ -221,6 +235,107 @@ void gc_add(void *start, void *end) {
|
221 | 235 | // Add this area to the linked list
|
222 | 236 | prev_area->next = area;
|
223 | 237 | }
|
| 238 | + |
| 239 | +#if MICROPY_GC_SPLIT_HEAP_AUTO |
| 240 | +// Try to automatically add a heap area large enough to |
| 241 | +// fulfill 'failed_alloc' |
| 242 | +STATIC bool gc_try_add_heap(size_t failed_alloc) |
| 243 | +{ |
| 244 | + // Equation to find a heap large enough to hold failed_alloc is derived from |
| 245 | + // the equation for gc_setup_area(), above: |
| 246 | + // |
| 247 | + // T = A + F + P |
| 248 | + // P == failed_alloc rounded up to next block size |
| 249 | + // A = P / (BLOCKS_PER_ATB * BYTES_PER_BLOCK |
| 250 | + // F = A * BLOCKS_PER_ATB / BLOCKS_PER_FTB |
| 251 | + size_t p_blocks = (failed_alloc + BYTES_PER_BLOCK - 1) / BYTES_PER_BLOCK; |
| 252 | + // Round up to use full allocation block(s) |
| 253 | + p_blocks = (p_blocks + BLOCKS_PER_ATB - 1) & ~(BLOCKS_PER_ATB - 1); |
| 254 | + |
| 255 | + // Unclear why these extra blocks needed, but necessary for the new heap |
| 256 | + // pool to be large enough |
| 257 | + p_blocks += BLOCKS_PER_ATB; |
| 258 | + |
| 259 | + size_t p = p_blocks * BYTES_PER_BLOCK; // should match gc_pool_end - gc_pool_start |
| 260 | + |
| 261 | + size_t a = p_blocks / BLOCKS_PER_ATB; |
| 262 | + |
| 263 | + #if MICROPY_ENABLE_FINALISER |
| 264 | + // should match gc_finaliser_table_byte_len |
| 265 | + size_t f = (p_blocks + BLOCKS_PER_FTB - 1) / BLOCKS_PER_FTB; |
| 266 | + #else |
| 267 | + size_t f = 0; |
| 268 | + #endif |
| 269 | + |
| 270 | + // T = ... (plus implementation overheads) |
| 271 | + size_t needed = p + a + f + sizeof(mp_state_mem_area_t) + ALLOC_TABLE_GAP_BYTE; |
| 272 | + |
| 273 | + size_t avail = gc_get_max_new_split(); |
| 274 | + |
| 275 | + DEBUG_printf("gc_try_add_heap failed_alloc " UINT_FMT ", " |
| 276 | + "p " UINT_FMT " bytes " UINT_FMT " blocks, " |
| 277 | + "a " UINT_FMT " bytes, " |
| 278 | + "f " UINT_FMT " bytes, " |
| 279 | + "needed " UINT_FMT ", avail " UINT_FMT " bytes \n", |
| 280 | + failed_alloc, |
| 281 | + p, |
| 282 | + p_blocks, |
| 283 | + a, |
| 284 | + f, |
| 285 | + needed, |
| 286 | + avail); |
| 287 | + |
| 288 | + if (avail < needed) { |
| 289 | + return false; // Can't fit |
| 290 | + } |
| 291 | + |
| 292 | + // Deciding how much to grow the total heap by each time is tricky: |
| 293 | + // |
| 294 | + // - Grow by too small amounts, leads to heap fragmentation issues. |
| 295 | + // |
| 296 | + // - Grow by too large amounts, may lead to system heap running out of |
| 297 | + // space. |
| 298 | + // |
| 299 | + // Currently, this implementation is: |
| 300 | + // |
| 301 | + // - At minimum, aim to double the total heap size each time we add a new |
| 302 | + // heap. i.e. without any large single allocations, total size will be |
| 303 | + // 64KB -> 128KB -> 256KB -> 512KB -> 1MB, etc |
| 304 | + // |
| 305 | + // - If the failed allocation is too large to fit in that size, the new |
| 306 | + // heap is made exactly large enough for that allocation. Future growth |
| 307 | + // will double the total heap size again. |
| 308 | + // |
| 309 | + // - If the new heap won't fit in the available free space, add the largest |
| 310 | + // new heap that will fit (this may lead to failed system heap allocations |
| 311 | + // elsewhere, but some allocation will likely fail in this circumstance!) |
| 312 | + size_t total_heap = 0; |
| 313 | + for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); |
| 314 | + area != NULL; |
| 315 | + area = NEXT_AREA(area)) { |
| 316 | + total_heap += area->gc_pool_end - area->gc_alloc_table_start; |
| 317 | + total_heap += ALLOC_TABLE_GAP_BYTE + sizeof(mp_state_mem_area_t); |
| 318 | + } |
| 319 | + |
| 320 | + DEBUG_printf("total_heap " UINT_FMT " bytes\n", total_heap); |
| 321 | + |
| 322 | + size_t to_alloc = MIN(avail, MAX(total_heap, needed)); |
| 323 | + |
| 324 | + mp_state_mem_area_t *new_heap = malloc(to_alloc); |
| 325 | + |
| 326 | + DEBUG_printf("gc_try_add_heap malloc " UINT_FMT " = %p\n", |
| 327 | + to_alloc, new_heap); |
| 328 | + |
| 329 | + if (new_heap == NULL) { |
| 330 | + return false; |
| 331 | + } |
| 332 | + |
| 333 | + gc_add(new_heap, (void *)new_heap + to_alloc); |
| 334 | + |
| 335 | + return true; |
| 336 | +} |
| 337 | +#endif |
| 338 | + |
224 | 339 | #endif
|
225 | 340 |
|
226 | 341 | void gc_lock(void) {
|
@@ -377,7 +492,13 @@ STATIC void gc_sweep(void) {
|
377 | 492 | #endif
|
378 | 493 | // free unmarked heads and their tails
|
379 | 494 | int free_tail = 0;
|
| 495 | + #if MICROPY_GC_SPLIT_HEAP_AUTO |
| 496 | + mp_state_mem_area_t *prev_area = NULL; |
| 497 | + #endif |
380 | 498 | for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) {
|
| 499 | + #if MICROPY_GC_SPLIT_HEAP_AUTO |
| 500 | + bool area_empty = true; |
| 501 | + #endif |
381 | 502 | for (size_t block = 0; block < area->gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) {
|
382 | 503 | MICROPY_GC_HOOK_LOOP(block);
|
383 | 504 | switch (ATB_GET_KIND(area, block)) {
|
@@ -424,9 +545,23 @@ STATIC void gc_sweep(void) {
|
424 | 545 | case AT_MARK:
|
425 | 546 | ATB_MARK_TO_HEAD(area, block);
|
426 | 547 | free_tail = 0;
|
| 548 | + #if MICROPY_GC_SPLIT_HEAP_AUTO |
| 549 | + area_empty = false; |
| 550 | + #endif |
427 | 551 | break;
|
428 | 552 | }
|
429 | 553 | }
|
| 554 | + |
| 555 | + #if MICROPY_GC_SPLIT_HEAP_AUTO |
| 556 | + // Free any empty area, aside from the first one |
| 557 | + if (area_empty && prev_area != NULL) { |
| 558 | + DEBUG_printf("gc_sweep free empty area %p\n", area); |
| 559 | + NEXT_AREA(prev_area) = NEXT_AREA(area); |
| 560 | + free(area); |
| 561 | + area = prev_area; |
| 562 | + } |
| 563 | + prev_area = area; |
| 564 | + #endif |
430 | 565 | }
|
431 | 566 | }
|
432 | 567 |
|
@@ -609,6 +744,9 @@ void *gc_alloc(size_t n_bytes, unsigned int alloc_flags) {
|
609 | 744 | size_t start_block;
|
610 | 745 | size_t n_free;
|
611 | 746 | int collected = !MP_STATE_MEM(gc_auto_collect_enabled);
|
| 747 | +#if MICROPY_GC_SPLIT_HEAP_AUTO |
| 748 | + bool added = false; |
| 749 | +#endif |
612 | 750 |
|
613 | 751 | #if MICROPY_GC_ALLOC_THRESHOLD
|
614 | 752 | if (!collected && MP_STATE_MEM(gc_alloc_amount) >= MP_STATE_MEM(gc_alloc_threshold)) {
|
@@ -654,6 +792,12 @@ void *gc_alloc(size_t n_bytes, unsigned int alloc_flags) {
|
654 | 792 | GC_EXIT();
|
655 | 793 | // nothing found!
|
656 | 794 | if (collected) {
|
| 795 | + #if MICROPY_GC_SPLIT_HEAP_AUTO |
| 796 | + if (!added && gc_try_add_heap(n_bytes)) { |
| 797 | + added = true; |
| 798 | + continue; |
| 799 | + } |
| 800 | + #endif |
657 | 801 | return NULL;
|
658 | 802 | }
|
659 | 803 | DEBUG_printf("gc_alloc(" UINT_FMT "): no free mem, triggering GC\n", n_bytes);
|
@@ -1024,9 +1168,12 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) {
|
1024 | 1168 | void gc_dump_info(const mp_print_t *print) {
|
1025 | 1169 | gc_info_t info;
|
1026 | 1170 | gc_info(&info);
|
1027 |
| - mp_printf(print, "GC: total: %u, used: %u, free: %u\n", |
| 1171 | + mp_printf(print, "GC: total: %u, used: %u, free: %u", |
1028 | 1172 | (uint)info.total, (uint)info.used, (uint)info.free);
|
1029 |
| - mp_printf(print, " No. of 1-blocks: %u, 2-blocks: %u, max blk sz: %u, max free sz: %u\n", |
| 1173 | + #if MICROPY_GC_SPLIT_HEAP_AUTO |
| 1174 | + mp_printf(print, ", max new split: %u", (uint)gc_get_max_new_split()); |
| 1175 | + #endif |
| 1176 | + mp_printf(print, "\n No. of 1-blocks: %u, 2-blocks: %u, max blk sz: %u, max free sz: %u\n", |
1030 | 1177 | (uint)info.num_1block, (uint)info.num_2block, (uint)info.max_block, (uint)info.max_free);
|
1031 | 1178 | }
|
1032 | 1179 |
|
|
0 commit comments