@@ -238,6 +238,9 @@ void gc_add(void *start, void *end) {
238
238
}
239
239
240
240
#if MICROPY_GC_SPLIT_HEAP_AUTO
241
+
242
+ size_t gc_heap_sys_reserve = MP_PLAT_DEFAULT_HEAP_SYS_RESERVE ;
243
+
241
244
// Try to automatically add a heap area large enough to fulfill 'failed_alloc'.
242
245
STATIC bool gc_try_add_heap (size_t failed_alloc ) {
243
246
// 'needed' is the size of a heap large enough to hold failed_alloc, with
@@ -249,55 +252,82 @@ STATIC bool gc_try_add_heap(size_t failed_alloc) {
249
252
// rounding up of partial block sizes).
250
253
size_t needed = failed_alloc + MAX (2048 , failed_alloc * 13 / 512 );
251
254
252
- size_t avail = gc_get_max_new_split ();
255
+ size_t max_new_split = gc_get_max_new_split ();
253
256
254
257
DEBUG_printf ("gc_try_add_heap failed_alloc " UINT_FMT ", "
255
- "needed " UINT_FMT ", avail " UINT_FMT " bytes \n" ,
258
+ "needed " UINT_FMT ", max_new_split " UINT_FMT " bytes \n" ,
256
259
failed_alloc ,
257
260
needed ,
258
- avail );
261
+ max_new_split );
259
262
260
- if (avail < needed ) {
263
+ if (max_new_split < needed ) {
261
264
// Can't fit this allocation, or system heap has nearly run out anyway
262
265
return false;
263
266
}
264
267
265
- // Deciding how much to grow the total heap by each time is tricky:
268
+ // Measure the total Python heap size
269
+ size_t total_heap = 0 ;
270
+ for (mp_state_mem_area_t * area = & MP_STATE_MEM (area );
271
+ area != NULL ;
272
+ area = NEXT_AREA (area )) {
273
+ total_heap += area -> gc_pool_end - area -> gc_alloc_table_start ;
274
+ total_heap += ALLOC_TABLE_GAP_BYTE + sizeof (mp_state_mem_area_t );
275
+ }
276
+
277
+ // Deciding how much to allocate for the new "split" heap is tricky:
266
278
//
267
279
// - Grow by too small amounts, leads to heap fragmentation issues.
268
280
//
269
281
// - Grow by too large amounts, may lead to system heap running out of
270
282
// space.
271
283
//
272
- // Currently, this implementation is:
273
- //
274
- // - At minimum, aim to double the total heap size each time we add a new
275
- // heap. i.e. without any large single allocations, total size will be
276
- // 64KB -> 128KB -> 256KB -> 512KB -> 1MB, etc
277
- //
278
- // - If the failed allocation is too large to fit in that size, the new
279
- // heap is made exactly large enough for that allocation. Future growth
280
- // will double the total heap size again.
284
+
285
+ // Start by choosing the current total Python heap size for the new heap.
286
+ // With no other constraints, the total Python heap size would double each
287
+ // time: i.e 64KB -> 128KB -> 256KB -> 512KB -> 1MB, etc. This avoids
288
+ // fragmentation where possible.
289
+
290
+ size_t new_heap_size = total_heap ;
291
+
292
+ // If this "greedy" size will cut free system heap below
293
+ // gc_heap_sys_reserve then reduce it to conserve that limit.
281
294
//
282
- // - If the new heap won't fit in the available free space, add the largest
283
- // new heap that will fit (this may lead to failed system heap allocations
284
- // elsewhere, but some allocation will likely fail in this circumstance!)
285
- size_t total_heap = 0 ;
286
- for (mp_state_mem_area_t * area = & MP_STATE_MEM (area );
287
- area != NULL ;
288
- area = NEXT_AREA (area )) {
289
- total_heap += area -> gc_pool_end - area -> gc_alloc_table_start ;
290
- total_heap += ALLOC_TABLE_GAP_BYTE + sizeof (mp_state_mem_area_t );
295
+ // (gc_heap_sys_reserve still isn't a hard limit, if the only
296
+ // options are returning a MemoryError to Python or using up the reserved
297
+ // system heap space then MicroPython will use up the reserved system heap
298
+ // space.)
299
+
300
+ size_t total_free = gc_get_total_free ();
301
+ if (total_free < gc_heap_sys_reserve ) {
302
+ new_heap_size = needed ;
303
+ } else if (total_free - new_heap_size < gc_heap_sys_reserve ) {
304
+ new_heap_size = total_free - gc_heap_sys_reserve ;
305
+ }
306
+
307
+ // If this size is smaller than the size 'needed' to avoid an immediate
308
+ // MemoryError, increase to this size so the current failing allocation
309
+ // can succeed.
310
+
311
+ if (new_heap_size < needed ) {
312
+ new_heap_size = needed ;
291
313
}
292
314
293
- DEBUG_printf ("total_heap " UINT_FMT " bytes\n" , total_heap );
315
+ // If this size won't fit in the largest free system heap block, decrease
316
+ // it so it will fit (note: due to the check earlier, we already know
317
+ // max_new_split is large enough to hold 'needed')
318
+
319
+ if (new_heap_size > max_new_split ) {
320
+ new_heap_size = max_new_split ;
321
+ }
294
322
295
- size_t to_alloc = MIN (avail , MAX (total_heap , needed ));
323
+ DEBUG_printf ("total_heap " UINT_FMT " total_free "
324
+ UINT_FMT " TRY_RESERVE_SYSTEM_HEAP " UINT_FMT " bytes\n" ,
325
+ total_heap , total_free , gc_heap_sys_reserve );
296
326
297
- mp_state_mem_area_t * new_heap = MP_PLAT_ALLOC_HEAP (to_alloc );
327
+ mp_state_mem_area_t * new_heap = MP_PLAT_ALLOC_HEAP (new_heap_size );
298
328
299
329
DEBUG_printf ("MP_PLAT_ALLOC_HEAP " UINT_FMT " = %p\n" ,
300
- to_alloc , new_heap );
330
+ new_heap_size , new_heap );
301
331
302
332
if (new_heap == NULL ) {
303
333
// This should only fail:
@@ -307,7 +337,7 @@ STATIC bool gc_try_add_heap(size_t failed_alloc) {
307
337
return false;
308
338
}
309
339
310
- gc_add (new_heap , (void * )new_heap + to_alloc );
340
+ gc_add (new_heap , (void * )new_heap + ne
FEE1
w_heap_size );
311
341
312
342
return true;
313
343
}
0 commit comments