A Deep Dive into MicroPython's Garbage Collection

MicroPython is a lean and efficient implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimised to run on microcontrollers and constrained systems. One of the crucial features in MicroPython is the garbage collector (gc). The garbage collector is responsible for automatically reclaiming memory that is no longer in use by the program, which is essential in resource - constrained environments like microcontrollers where memory is limited. This blog post will explore the fundamental concepts, usage methods, common practices, and best practices of MicroPython's gc module.

Table of Contents#

  1. Fundamental Concepts of MicroPython GC
  2. Usage Methods
  3. Common Practices
  4. Best Practices
  5. Conclusion
  6. References

Fundamental Concepts of MicroPython GC#

What is Garbage Collection?#

Garbage collection is an automatic memory management technique. In programming, when we create objects (variables, lists, etc.), memory is allocated to store these objects. When these objects are no longer needed, the memory they occupy should be freed up so that it can be used by other parts of the program. The garbage collector identifies these "garbage" objects (objects that are no longer reachable from the program) and reclaims the memory they use.

How MicroPython's GC Works#

MicroPython uses a mark - and - sweep garbage collection algorithm. The mark phase involves traversing all the objects that are still reachable from the program's roots (such as global variables, local variables in active functions). The reachable objects are marked as "alive". In the sweep phase, the garbage collector scans through the entire memory space and frees up the memory of all the unmarked objects, which are considered garbage.

Usage Methods#

Importing the gc Module#

To use the garbage collection features in MicroPython, you first need to import the gc module.

import gc

Manual Garbage Collection#

You can trigger a garbage collection cycle manually using the collect() function.

import gc
 
# Manually trigger garbage collection
gc.collect()

Checking Available Memory#

You can check the amount of free memory available using the mem_free() function.

import gc
 
# Get the amount of free memory
free_memory = gc.mem_free()
print(f"Free memory: {free_memory} bytes")

Checking Allocated Memory#

The mem_alloc() function can be used to check the amount of memory that is currently allocated.

import gc
 
# Get the amount of allocated memory
allocated_memory = gc.mem_alloc()
print(f"Allocated memory: {allocated_memory} bytes")

Enabling and Disabling the Garbage Collector#

You can enable or disable the automatic garbage collector using the enable() and disable() functions respectively.

import gc
 
# Disable the garbage collector
gc.disable()
 
# Do some memory - intensive operations
 
# Enable the garbage collector again
gc.enable()

Common Practices#

Monitoring Memory Usage#

Regularly monitoring the available and allocated memory can help you identify memory leaks in your program. A memory leak occurs when memory is continuously allocated but not properly freed. You can use the mem_free() and mem_alloc() functions in a loop to monitor memory usage over time.

import gc
import time
 
while True:
    free_memory = gc.mem_free()
    allocated_memory = gc.mem_alloc()
    print(f"Free memory: {free_memory} bytes, Allocated memory: {allocated_memory} bytes")
    time.sleep(1)

Triggering GC in Memory - Intensive Operations#

If you are performing operations that allocate a large amount of memory, it is a good idea to trigger a garbage collection cycle before and after these operations.

import gc
 
# Trigger garbage collection before a memory - intensive operation
gc.collect()
 
# Memory - intensive operation
large_list = [i for i in range(1000)]
 
# Trigger garbage collection after the operation
gc.collect()

Best Practices#

Minimise Memory Allocation#

In resource - constrained environments, it is important to minimise memory allocation. Reuse existing objects instead of creating new ones whenever possible. For example, instead of creating a new list every time, you can clear and reuse an existing list.

import gc
 
my_list = []
for i in range(10):
    my_list.clear()
    # Populate the list with new data
    my_list.extend([j for j in range(10)])
    # Do some operations with the list
    #...

Use Generators#

Generators are a memory - efficient alternative to lists. Instead of creating a list with all the elements at once, generators generate elements on - the - fly.

import gc
 
# Using a generator
gen = (i for i in range(1000))
for num in gen:
    # Do something with num
    pass

Be Careful with Recursion#

Recursion can lead to excessive memory usage because each recursive call adds a new stack frame to the call stack. In MicroPython, it is better to use iterative solutions instead of recursive ones when possible.

Conclusion#

MicroPython's garbage collector is a powerful tool for managing memory in resource - constrained environments. By understanding the fundamental concepts, usage methods, common practices, and best practices, you can write more memory - efficient MicroPython programs. Regularly monitoring memory usage, triggering garbage collection at appropriate times, minimising memory allocation, and using memory - efficient data structures are all important steps in optimising your MicroPython code.

References#