4.19. Grupos de memoria¶
Una cámara que mantiene tres fotogramas a resolución completa en un grupo de búferes de fotogramas (frame buffer), ejecuta un búfer de vista previa independiente en paralelo y además tiene espacio para un script de Python y sus objetos está gestionando más memoria de la que un único bloque de RAM del MCU podría proporcionar. MicroPython logra que todo quepa repartiéndolo entre los distintos tipos de memoria que ofrece el MCU, y dirigiendo cada tipo de asignación al tipo de memoria que realmente necesita.
4.19.1. Tipos de memoria¶
Un MCU moderno de la OpenMV Cam expone cuatro tipos de memoria distintos. El primero es invisible para la aplicación; los otros tres son grupos de los que pueden provenir las asignaciones.
La caché de datos de la CPU – una región de memoria pequeña y muy rápida que se sitúa entre la CPU y el resto de la RAM. Cuando la CPU lee o escribe un valor de la memoria principal, la caché conserva automáticamente una copia, de modo que los accesos repetidos a los mismos datos permanecen en la caché y nunca pagan el coste de acudir a una memoria más lenta. La caché no es un grupo del que provengan las asignaciones. Es transparente para la aplicación: simplemente hace que el resto de la RAM parezca más rápida en la práctica de lo que sugeriría su latencia bruta, hasta el punto en que un conjunto de trabajo deja de caber en ella.
Memoria del procesador estrechamente acoplada – un pequeño bloque de RAM conectado directamente a la CPU sin ningún bus de por medio. Acceso en un solo ciclo, nunca falla, nunca espera. Las asignaciones que realmente necesitan la memoria más rápida posible – donde cada ciclo de latencia importa – provienen de este grupo.
Memoria rápida en chip – desde unos pocos cientos de kilobytes hasta cerca de un megabyte de RAM, integrada en el encapsulado del MCU. Baja latencia, alto ancho de banda, pero de tamaño limitado. El montículo (heap) de MicroPython reside aquí para que los accesos a objetos de Python sigan siendo rápidos; los búferes de trabajo más pequeños que la CPU usa con frecuencia comparten el grupo.
Memoria masiva más lenta – en placas que combinan el MCU con un chip de memoria externo, decenas de megabytes de RAM fuera del chip a los que se accede a través del bus externo. Mucho más grande, pero cada acceso tarda más que en la memoria en chip; la caché de datos oculta gran parte de ese coste para los conjuntos de trabajo que puede albergar, y la diferencia se nota en operaciones que recorren datos demasiado grandes para almacenarse en caché. Se usa para asignaciones que tienen que ser grandes y que la CPU puede tolerar a menor velocidad – lo más importante, el grupo de búferes de fotogramas.
Las placas de la familia se sitúan en un espectro: algunas tienen solo RAM en chip; otras combinan RAM en chip con un bloque externo mucho mayor. Cada uno de los tres tipos asignables se trata como un grupo de memoria – un bloque del que provienen las asignaciones – y se etiqueta para que cada solicitud pueda pedir el tipo de memoria que realmente necesita.
4.19.2. El búfer de fotogramas principal¶
El búfer de fotogramas que respalda a snapshot() no pide memoria rápida. Pide memoria suficiente – nada más. Eso lo coloca en el grupo que sea más grande, de modo que en una placa con memoria tanto en chip como externa el búfer de fotogramas acaba en el bloque externo.
Un búfer de fotogramas a resolución completa y con triple búfer es demasiado grande para caber en el grupo rápido en chip en la mayoría de las piezas; el grupo más grande es el único que puede albergarlo. La caché de datos de la CPU oculta gran parte del coste por acceso cuando la aplicación procesa la imagen, y el motor DMA que llena el búfer de fotogramas desde el sensor sigue el ritmo de la tasa de datos del sensor en cualquier caso.
El tamaño exacto que ocupa el búfer de fotogramas se calcula a partir de los valores actuales de pixformat(), framesize() y del recuento de framebuffers(); crece o se reduce cada vez que cualquiera de ellos cambia.
4.19.3. Búferes de fotogramas de sensores secundarios¶
Una segunda instancia de CSI obtiene su propio búfer de fotogramas, asignado desde el mismo grupo que usa la principal. El grupo es compartido; los búferes son independientes. La huella del secundario suele ser mucho menor que la del principal, porque los sensores secundarios funcionan a resoluciones más bajas, de modo que la memoria adicional que ocupa el segundo búfer de fotogramas es una pequeña fracción de la del principal.
4.19.4. El búfer de fotogramas del flujo¶
El búfer de vista previa de imagen es la excepción. No se asigna desde ninguno de los grupos en tiempo de ejecución; es una región fija reservada en tiempo de compilación, con una dirección conocida y un tamaño conocido. Eso mantiene la ruta de vista previa apartada de cualquier otra asignación: la región existe desde el arranque y nunca se mueve.
4.19.5. El montículo de MicroPython¶
Los objetos de Python – variables, listas, diccionarios, instancias de clase, el envoltorio Image que devuelve una llamada a snapshot(), cada cadena y tupla que crea la aplicación – residen en el montículo (heap) con recolección de basura de MicroPython, que es independiente de los grupos de memoria de la cámara. El montículo con recolección de basura (GC) es una región de memoria que MicroPython gestiona por sí mismo: el código de Python asigna de él implícitamente cada vez que se crea un objeto, y MicroPython escanea periódicamente el montículo y recupera el espacio ocupado por objetos a los que la aplicación ya no hace referencia, de modo que la aplicación nunca tiene que liberar nada a mano.
Se reserva una región dedicada para el montículo GC en el arranque, típicamente situada en la memoria rápida en chip para que el acceso de Python siga siendo rápido, con un desbordamiento opcional hacia el bloque externo más grande en las placas que necesitan más margen para grandes estructuras de datos.
El Image que devuelve snapshot() es un pequeño objeto envoltorio en el montículo GC; los datos de píxeles subyacentes residen en el búfer de fotogramas en uno de los grupos de la cámara. Ambos nunca compiten por la misma memoria.
4.19.6. Juntándolo todo¶
Dirigir cada tipo de asignación al grupo correcto – los búferes grandes al grupo más grande donde caben, los datos sensibles a la latencia a los grupos más rápidos, el montículo de Python a su propia región, la vista previa a su ranura reservada – es lo que hace posible ejecutar una canalización de captura a resolución completa, un canal de vista previa y un script de Python no trivial uno junto a otro en piezas que tienen solo unos pocos megabytes de memoria rápida en total.