Skip to content

Commit 516b09e

Browse files
committed
py, gc: Further reduce heap fragmentation with new, faster gc alloc.
The heap allocation is now exactly as it was before the "faster gc alloc" patch, but it's still nearly as fast. It is fixed by being careful to always update the "last free block" pointer whenever the heap changes (eg free or realloc). Tested on all tests by enabling EXTENSIVE_HEAP_PROFILING in py/gc.c: old and new allocator have exactly the same behaviour, just the new one is much faster.
1 parent b796e3d commit 516b09e

File tree

1 file changed

+58
-2
lines changed

1 file changed

+58
-2
lines changed

py/gc.c

+58-2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646
#define DEBUG_printf(...) (void)0
4747
#endif
4848

49+
// make this 1 to dump the heap each time it changes
50+
#define EXTENSIVE_HEAP_PROFILING (0)
51+
4952
#define WORDS_PER_BLOCK (4)
5053
#define BYTES_PER_BLOCK (WORDS_PER_BLOCK * BYTES_PER_WORD)
5154
#define STACK_SIZE (64) // tunable; minimum is 1
@@ -405,7 +408,8 @@ void *gc_alloc(mp_uint_t n_bytes, bool has_finaliser) {
405408
// Set last free ATB index to block after last block we found, for start of
406409
// next scan. To reduce fragmentation, we only do this if we were looking
407410
// for a single free block, which guarantees that there are no free blocks
408-
// before this one.
411+
// before this one. Also, whenever we free or shink a block we must check
412+
// if this index needs adjusting (see gc_realloc and gc_free).
409413
if (n_free == 1) {
410414
gc_last_free_atb_index = (i + 1) / BLOCKS_PER_ATB;
411415
}
@@ -439,6 +443,10 @@ void *gc_alloc(mp_uint_t n_bytes, bool has_finaliser) {
439443
}
440444
#endif
441445

446+
#if EXTENSIVE_HEAP_PROFILING
447+
gc_dump_alloc_table();
448+
#endif
449+
442450
return ret_ptr;
443451
}
444452

@@ -465,11 +473,20 @@ void gc_free(void *ptr_in) {
465473
if (VERIFY_PTR(ptr)) {
466474
mp_uint_t block = BLOCK_FROM_PTR(ptr);
467475
if (ATB_GET_KIND(block) == AT_HEAD) {
476+
// set the last_free pointer to this block if it's earlier in the heap
477+
if (block / BLOCKS_PER_ATB < gc_last_free_atb_index) {
478+
gc_last_free_atb_index = block / BLOCKS_PER_ATB;
479+
}
480+
468481
// free head and all of its tail blocks
469482
do {
470483
ATB_ANY_TO_FREE(block);
471484
block += 1;
472485
} while (ATB_GET_KIND(block) == AT_TAIL);
486+
487+
#if EXTENSIVE_HEAP_PROFILING
488+
gc_dump_alloc_table();
489+
#endif
473490
}
474491
}
475492
}
@@ -581,6 +598,16 @@ void *gc_realloc(void *ptr_in, mp_uint_t n_bytes) {
581598
for (mp_uint_t bl = block + new_blocks; ATB_GET_KIND(bl) == AT_TAIL; bl++) {
582599
ATB_ANY_TO_FREE(bl);
583600
}
601+
602+
// set the last_free pointer to end of this block if it's earlier in the heap
603+
if ((block + new_blocks) / BLOCKS_PER_ATB < gc_last_free_atb_index) {
604+
gc_last_free_atb_index = (block + new_blocks) / BLOCKS_PER_ATB;
605+
}
606+
607+
#if EXTENSIVE_HEAP_PROFILING
608+
gc_dump_alloc_table();
609+
#endif
610+
584611
return ptr_in;
585612
}
586613

@@ -595,6 +622,10 @@ void *gc_realloc(void *ptr_in, mp_uint_t n_bytes) {
595622
// zero out the additional bytes of the newly allocated blocks (see comment above in gc_alloc)
596623
memset((byte*)ptr_in + n_bytes, 0, new_blocks * BYTES_PER_BLOCK - n_bytes);
597624

625+
#if EXTENSIVE_HEAP_PROFILING
626+
gc_dump_alloc_table();
627+
#endif
628+
598629
return ptr_in;
599630
}
600631

@@ -628,9 +659,34 @@ void gc_dump_info() {
628659
}
629660

630661
void gc_dump_alloc_table(void) {
662+
static const mp_uint_t DUMP_BYTES_PER_LINE = 64;
663+
#if !EXTENSIVE_HEAP_PROFILING
664+
// When comparing heap output we don't want to print the starting
665+
// pointer of the heap because it changes from run to run.
631666
printf("GC memory layout; from %p:", gc_pool_start);
667+
#endif
632668
for (mp_uint_t bl = 0; bl < gc_alloc_table_byte_len * BLOCKS_PER_ATB; bl++) {
633-
if (bl % 64 == 0) {
669+
if (bl % DUMP_BYTES_PER_LINE == 0) {
670+
// a new line of blocks
671+
#if EXTENSIVE_HEAP_PROFILING
672+
{
673+
// check if this line contains only free blocks
674+
bool only_free_blocks = true;
675+
for (mp_uint_t bl2 = bl; bl2 < gc_alloc_table_byte_len * BLOCKS_PER_ATB && bl2 < bl + DUMP_BYTES_PER_LINE; bl2++) {
676+
if (ATB_GET_KIND(bl2) != AT_FREE) {
677+
678+
only_free_blocks = false;
679+
break;
680+
}
681+
}
682+
if (only_free_blocks) {
683+
// line contains only free blocks, so skip printing it
684+
bl += DUMP_BYTES_PER_LINE - 1;
685+
continue;
686+
}
687+
}
688+
#endif
689+
// print header for new line of blocks
634690
printf("\n%04x: ", (uint)bl);
635691
}
636692
int c = ' ';

0 commit comments

Comments
 (0)