micropython-gestion-memoria-garbage-collector

Gestión de memoria en MicroPython: GC y mem_info

  • 6 min

La memoria en MicroPython es un recurso limitado que debemos controlar para evitar errores en tiempo de ejecución.

Si vienes de programar en PC, probablemente nunca te has preocupado por la memoria RAM. Tienes gigabytes disponibles. Pero en el mundo de los microcontroladores, la realidad es muy distinta.

  • ESP8266: Apenas unos ~40 KB libres para el usuario.
  • Raspberry Pi Pico: Unos ~200 KB.
  • ESP32: Unos ~520 KB (aunque fragmentados).

Puede parecer suficiente, pero MicroPython es un lenguaje de alto nivel: cada número entero, cada lista y cada objeto consume recursos. Si no somos cuidadosos, nos encontraremos muy pronto con el temido error:

MemoryError: memory allocation failed

En este artículo vamos a abrir el capó para ver cómo gestiona MicroPython la memoria y cómo usar el Garbage Collector a nuestro favor.

El heap y el stack

Sin entrar en ingeniería de computadores profunda, debemos saber que la RAM se divide principalmente en dos zonas:

  1. Stack (Pila): Es una zona rígida y pequeña. Aquí se guardan las llamadas a funciones y las variables locales simples. Se limpia sola al salir de la función.
  2. Heap (Montón): Es la “piscina” de memoria libre. Aquí es donde viven los objetos grandes: listas, diccionarios, instancias de clases y el propio código de tu programa.

Cuando creas una lista mi_lista = [1, 2, 3], MicroPython busca un hueco libre en el Heap y lo ocupa. El problema viene cuando borramos cosas y creamos nuevas desordenadamente.

Diagnóstico: micropython.mem_info()

No puedes optimizar lo que no puedes medir. MicroPython incluye una herramienta de diagnóstico muy potente dentro del módulo micropython.

import micropython
import gc

# Forzamos una limpieza antes de medir
gc.collect()

# Imprimimos el informe de memoria
micropython.mem_info()
Copied!

La salida será algo parecido a esto:

stack: 2128 out of 15360
GC: total: 2072064, used: 4224, free: 2067840
 No. of 1-blocks: 14, 2-blocks: 6, max blk sz: 264, max free sz: 129218
Copied!

¿Qué significa esto?

  • Stack: Nos dice cuánto estamos usando de la pila. Si esto se llena (por ejemplo, con una recursividad infinita), tendremos un RuntimeError: maximum recursion depth exceeded.
  • GC total: La memoria RAM total disponible para el Heap.
  • used / free: Lo que estamos gastando y lo que nos queda.

Si ejecutamos micropython.mem_info(1) (con un 1 como argumento), nos dará un mapa visual de la memoria, donde cada carácter representa un bloque de memoria y su estado. Es útil para ver visualmente la fragmentación.

El recolector de basura (gc)

A diferencia de C++, donde tú tienes que liberar la memoria manualmente (free()), MicroPython tiene un conserje automático: el Garbage Collector (GC).

Su trabajo es recorrer la memoria periódicamente, buscar objetos que ya no se usan (que no tienen ninguna variable apuntando a ellos) y liberar ese espacio para que pueda ser reutilizado.

¿Cuándo actúa el GC?

Por defecto, el GC es perezoso. Solo se activa cuando intenta reservar memoria nueva y no encuentra espacio.

  1. Pides crear una lista grande.
  2. El sistema ve que no cabe.
  3. Pausa tu programa.
  4. Ejecuta el GC (limpia).
  5. Intenta asignar la lista de nuevo.

Este proceso automático tiene un riesgo: toma tiempo (varios milisegundos). Si esto ocurre justo cuando estás generando una señal PWM delicada o leyendo un sensor rápido, puedes tener problemas de timing.

Control manual del GC

Para proyectos avanzados, nosotros tomamos el control. Es buena práctica importar el módulo gc y gestionar la limpieza nosotros mismos.

import gc

# 1. Consultar memoria libre (en bytes)
print("Memoria libre:", gc.mem_free())

# 2. Consultar memoria usada
print("Memoria usada:", gc.mem_alloc())

# 3. Forzar una limpieza MANUAL
# Es muy recomendable hacerlo después de importar librerías grandes
# o antes de iniciar procesos críticos.
gc.collect() 
print("Limpieza realizada.")
Copied!

Desactivar el GC en zonas críticas

Si tienes un trozo de código crítico donde no puedes permitirte que el procesador se pare a limpiar (ej. controlando neopixels o motores paso a paso a alta velocidad), puedes deshabilitarlo temporalmente.

import gc

gc.disable() # Apagamos el recolector

# --- INICIO ZONA CRÍTICA ---
# Aquí el código debe ser rápido y no crear muchos objetos
# Si llenas la RAM aquí, el programa fallará sin intentar limpiar
hacer_cosas_rapidas()
# --- FIN ZONA CRÍTICA ---

gc.enable() # Lo reactivamos
gc.collect() # Limpiamos lo que se haya ensuciado
Copied!

Fragmentación de la memoria

A veces, gc.mem_free() te dice que tienes 20 KB libres, pero intentas crear una lista de 5 KB y te da error. ¿Por qué?

Porque la memoria no es contigua. Imagina un aparcamiento (la RAM). Tienes 20 plazas libres, pero están salteadas (una aquí, dos allá). Si intentas aparcar un autobús (un objeto grande), no cabe, aunque la suma de plazas libres sea suficiente.

Cómo evitar la fragmentación

  1. Evita crear y destruir objetos en bucles:
# Crea varias cadenas temporales en cada vuelta
while True:
    print("Temperatura: " + str(leer_sensor()))

# Evita concatenarlas si solo quieres mostrarlas
while True:
    print("Temperatura:", leer_sensor())
Copied!
  1. Pre-asigna memoria: Si sabes que vas a necesitar una lista de 1000 elementos, créala al principio, no vayas haciendo .append() poco a poco.
# Buffer preasignado
mi_buffer = bytearray(1000)
Copied!
  1. Usa const(): Para valores que no cambian, la función micropython.const() permite que el compilador los trate como constantes. Puede evitar búsquedas y objetos globales innecesarios, aunque el ahorro concreto depende del port y del nombre utilizado.
from micropython import const
DELAY_MS = const(100)
Copied!
  1. Ejecuta gc.collect() en puntos controlados: Puede liberar objetos que ya no se usan y evita que la recolección ocurra en el peor momento. No compacta la memoria ni mueve los objetos que siguen vivos, así que no elimina por sí sola la fragmentación.

Cómo evitar problemas de memoria

La memoria en MicroPython es un recurso precioso.

  • Usa micropython.mem_info() para saber qué está pasando.
  • Usa gc.collect() estratégicamente para limpiar “cuando tú quieras”, no “cuando el sistema colapse”.
  • Ten miedo a la fragmentación: intenta reutilizar objetos en lugar de crear nuevos constantemente.

Controlando el GC, podrás ejecutar programas sorprendentemente complejos en chips de menos de 5 euros.