4.19. Pools de mémoire¶
Une caméra qui conserve trois trames en pleine résolution dans un pool de tampons d’image, exécute en parallèle un tampon de prévisualisation distinct et garde malgré tout de la place pour un script Python et ses objets jongle avec plus de mémoire qu’un seul bloc de RAM du MCU ne pourrait fournir. MicroPython fait tenir le tout en le répartissant entre les plusieurs types distincts de mémoire que le MCU propose, et en orientant chaque type d’allocation vers le type de mémoire dont il a réellement besoin.
4.19.1. Types de mémoire¶
Un MCU OpenMV Cam moderne expose quatre types distincts de mémoire. Le premier est invisible pour l’application ; les trois autres sont des pools d’où peuvent provenir les allocations.
Le cache de données du CPU – une petite région de mémoire très rapide située entre le CPU et le reste de la RAM. Lorsque le CPU lit ou écrit une valeur en mémoire principale, le cache en conserve automatiquement une copie, de sorte que les accès répétés aux mêmes données restent dans le cache et n’ont jamais à payer le coût d’un aller vers une mémoire plus lente. Le cache n’est pas un pool d’où proviennent les allocations. Il est transparent pour l’application – il fait simplement que le reste de la RAM paraît plus rapide en pratique que ne le laisserait supposer sa latence brute, jusqu’au point où un ensemble de travail cesse d’y tenir.
Mémoire de processeur étroitement couplée – un petit bloc de RAM câblé directement au CPU, sans bus intermédiaire. Accès en un seul cycle, jamais de défaut de cache, jamais d’attente. Les allocations qui ont véritablement besoin de la mémoire la plus rapide possible – où chaque cycle de latence compte – proviennent de ce pool.
Mémoire rapide sur puce – de quelques centaines de kilo-octets jusqu’à environ un méga-octet de RAM, intégrée au boîtier du MCU. Faible latence, bande passante élevée, mais taille limitée. Le tas (heap) MicroPython y réside afin que les accès aux objets Python restent rapides ; les tampons de travail plus petits que le CPU sollicite beaucoup partagent ce pool.
Mémoire de masse plus lente – sur les cartes qui associent au MCU une puce de mémoire externe, des dizaines de méga-octets de RAM hors puce accessibles via le bus externe. Bien plus volumineuse, mais chaque accès prend plus de temps que sur la mémoire sur puce ; le cache de données masque une grande partie de ce coût pour les ensembles de travail qu’il peut contenir, et l’écart se manifeste sur les opérations qui balaient des données trop grandes pour être mises en cache. Utilisée pour les allocations qui doivent être volumineuses et que le CPU peut tolérer à une vitesse plus lente – le plus important étant le pool de tampons d’image.
Les cartes de la famille se répartissent sur un spectre : certaines ne disposent que de RAM sur puce ; d’autres associent de la RAM sur puce à un bloc externe bien plus grand. Chacun des trois types allouables est traité comme un pool de mémoire – un bloc d’où proviennent les allocations – et étiqueté afin que chaque requête puisse demander le type de mémoire dont elle a réellement besoin.
4.19.2. Le tampon d’image principal¶
Le tampon d’image qui sous-tend snapshot() ne demande pas de mémoire rapide. Il demande suffisamment de mémoire – rien de plus. Cela le place dans le pool le plus grand, de sorte que sur une carte dotée à la fois de mémoire sur puce et de mémoire externe, le tampon d’image se retrouve dans le bloc externe.
Un tampon d’image en pleine résolution et à triple tampon est bien trop volumineux pour tenir dans le pool rapide sur puce de la plupart des composants ; le pool le plus grand est le seul capable de le contenir. Le cache de données du CPU masque une grande partie du coût par accès lorsque l’application traite l’image, et le moteur DMA qui remplit le tampon d’image à partir du capteur suit le débit de données du capteur dans tous les cas.
La taille exacte qu’occupe le tampon d’image est déterminée à partir des valeurs courantes de pixformat(), framesize() et du nombre de framebuffers() ; elle augmente ou diminue à chaque fois que l’une de ces valeurs change.
4.19.3. Tampons d’image de capteurs secondaires¶
Une seconde instance CSI obtient son propre tampon d’image, alloué depuis le même pool que celui utilisé par le principal. Le pool est partagé ; les tampons sont indépendants. L’empreinte du capteur secondaire est normalement bien plus petite que celle du principal, car les capteurs secondaires fonctionnent à des résolutions plus basses, de sorte que la mémoire supplémentaire qu’occupe le second tampon d’image ne représente qu’une petite fraction de celle du principal.
4.19.4. Le tampon d’image de flux¶
Le tampon de prévisualisation d’image constitue l’exception. Il n’est alloué depuis aucun des pools à l’exécution ; c’est une région fixe réservée au moment de la compilation, avec une adresse connue et une taille connue. Cela maintient le chemin de prévisualisation à l’écart de toutes les autres allocations – la région existe dès le démarrage et ne se déplace jamais.
4.19.5. Le tas MicroPython¶
Les objets Python – variables, listes, dictionnaires, instances de classe, l’enveloppe Image que retourne un appel à snapshot(), chaque chaîne et chaque tuple que l’application crée – résident sur le tas à ramasse-miettes de MicroPython, qui est distinct des pools de mémoire de la caméra. Le tas à ramasse-miettes (GC) est une région de mémoire que MicroPython gère lui-même : le code Python y alloue implicitement à chaque création d’objet, et MicroPython parcourt périodiquement le tas et récupère l’espace occupé par les objets que l’application ne référence plus, de sorte que l’application n’a jamais à libérer quoi que ce soit à la main.
Une région dédiée est réservée pour le tas GC au démarrage, généralement placée en mémoire rapide sur puce afin que l’accès Python reste rapide, avec un débordement optionnel vers le bloc externe plus grand sur les cartes qui ont besoin de davantage de marge pour de grandes structures de données.
L’objet Image retourné par snapshot() est un petit objet enveloppe sur le tas GC ; les données de pixels sous-jacentes résident dans le tampon d’image, dans l’un des pools de la caméra. Les deux ne se disputent jamais la même mémoire.
4.19.6. Assemblage de l’ensemble¶
Orienter chaque type d’allocation vers le bon pool – les gros tampons vers le pool le plus grand où ils tiennent, les données sensibles à la latence vers les pools les plus rapides, le tas Python vers sa propre région, la prévisualisation vers son emplacement réservé – est ce qui rend possible l’exécution simultanée d’un pipeline de capture en pleine résolution, d’un canal de prévisualisation et d’un script Python non trivial sur des composants ne disposant en tout que de quelques méga-octets de mémoire rapide.