diff --git a/py/gc.c b/py/gc.c
index 53bcb069a953c781b2448d7b8db3faf00ff10062..969b6edd29f864e553c4f6acac7e502130eedc84 100644
--- a/py/gc.c
+++ b/py/gc.c
@@ -46,6 +46,9 @@
 #define DEBUG_printf(...) (void)0
 #endif
 
+// make this 1 to dump the heap each time it changes
+#define EXTENSIVE_HEAP_PROFILING (0)
+
 #define WORDS_PER_BLOCK (4)
 #define BYTES_PER_BLOCK (WORDS_PER_BLOCK * BYTES_PER_WORD)
 #define STACK_SIZE (64) // tunable; minimum is 1
@@ -405,7 +408,8 @@ found:
     // Set last free ATB index to block after last block we found, for start of
     // next scan.  To reduce fragmentation, we only do this if we were looking
     // for a single free block, which guarantees that there are no free blocks
-    // before this one.
+    // before this one.  Also, whenever we free or shink a block we must check
+    // if this index needs adjusting (see gc_realloc and gc_free).
     if (n_free == 1) {
         gc_last_free_atb_index = (i + 1) / BLOCKS_PER_ATB;
     }
@@ -439,6 +443,10 @@ found:
     }
 #endif
 
+    #if EXTENSIVE_HEAP_PROFILING
+    gc_dump_alloc_table();
+    #endif
+
     return ret_ptr;
 }
 
@@ -465,11 +473,20 @@ void gc_free(void *ptr_in) {
     if (VERIFY_PTR(ptr)) {
         mp_uint_t block = BLOCK_FROM_PTR(ptr);
         if (ATB_GET_KIND(block) == AT_HEAD) {
+            // set the last_free pointer to this block if it's earlier in the heap
+            if (block / BLOCKS_PER_ATB < gc_last_free_atb_index) {
+                gc_last_free_atb_index = block / BLOCKS_PER_ATB;
+            }
+
             // free head and all of its tail blocks
             do {
                 ATB_ANY_TO_FREE(block);
                 block += 1;
             } while (ATB_GET_KIND(block) == AT_TAIL);
+
+            #if EXTENSIVE_HEAP_PROFILING
+            gc_dump_alloc_table();
+            #endif
         }
     }
 }
@@ -581,6 +598,16 @@ void *gc_realloc(void *ptr_in, mp_uint_t n_bytes) {
         for (mp_uint_t bl = block + new_blocks; ATB_GET_KIND(bl) == AT_TAIL; bl++) {
             ATB_ANY_TO_FREE(bl);
         }
+
+        // set the last_free pointer to end of this block if it's earlier in the heap
+        if ((block + new_blocks) / BLOCKS_PER_ATB < gc_last_free_atb_index) {
+            gc_last_free_atb_index = (block + new_blocks) / BLOCKS_PER_ATB;
+        }
+
+        #if EXTENSIVE_HEAP_PROFILING
+        gc_dump_alloc_table();
+        #endif
+
         return ptr_in;
     }
 
@@ -595,6 +622,10 @@ void *gc_realloc(void *ptr_in, mp_uint_t n_bytes) {
         // zero out the additional bytes of the newly allocated blocks (see comment above in gc_alloc)
         memset((byte*)ptr_in + n_bytes, 0, new_blocks * BYTES_PER_BLOCK - n_bytes);
 
+        #if EXTENSIVE_HEAP_PROFILING
+        gc_dump_alloc_table();
+        #endif
+
         return ptr_in;
     }
 
@@ -628,9 +659,34 @@ void gc_dump_info() {
 }
 
 void gc_dump_alloc_table(void) {
+    static const mp_uint_t DUMP_BYTES_PER_LINE = 64;
+    #if !EXTENSIVE_HEAP_PROFILING
+    // When comparing heap output we don't want to print the starting
+    // pointer of the heap because it changes from run to run.
     printf("GC memory layout; from %p:", gc_pool_start);
+    #endif
     for (mp_uint_t bl = 0; bl < gc_alloc_table_byte_len * BLOCKS_PER_ATB; bl++) {
-        if (bl % 64 == 0) {
+        if (bl % DUMP_BYTES_PER_LINE == 0) {
+            // a new line of blocks
+            #if EXTENSIVE_HEAP_PROFILING
+            {
+                // check if this line contains only free blocks
+                bool only_free_blocks = true;
+                for (mp_uint_t bl2 = bl; bl2 < gc_alloc_table_byte_len * BLOCKS_PER_ATB && bl2 < bl + DUMP_BYTES_PER_LINE; bl2++) {
+                    if (ATB_GET_KIND(bl2) != AT_FREE) {
+
+                        only_free_blocks = false;
+                        break;
+                    }
+                }
+                if (only_free_blocks) {
+                    // line contains only free blocks, so skip printing it
+                    bl += DUMP_BYTES_PER_LINE - 1;
+                    continue;
+                }
+            }
+            #endif
+            // print header for new line of blocks
             printf("\n%04x: ", (uint)bl);
         }
         int c = ' ';