4.19. Rezerve de memorie¶
O cameră care păstrează trei cadre la rezoluție completă într-o rezervă de tampoane de cadre (frame buffer), rulează în paralel un tampon separat de previzualizare și are în plus loc pentru un script Python și obiectele acestuia jonglează cu mai multă memorie decât ar putea oferi un singur bloc de RAM de pe MCU. MicroPython reușește să încapă totul răspândind datele pe mai multe tipuri distincte de memorie pe care le pune la dispoziție MCU-ul și direcționând fiecare tip de alocare către tipul de memorie de care are cu adevărat nevoie.
4.19.1. Tipuri de memorie¶
Un MCU modern pentru OpenMV Cam expune patru tipuri distincte de memorie. Primul este invizibil pentru aplicație; celelalte trei sunt rezerve din care pot proveni alocările.
Memoria cache de date a CPU-ului – o regiune de memorie mică și foarte rapidă, situată între CPU și restul memoriei RAM. Atunci când CPU-ul citește sau scrie o valoare din memoria principală, cache-ul păstrează automat o copie, astfel încât accesările repetate ale acelorași date rămân în cache și nu suportă niciodată costul deplasării către memoria mai lentă. Cache-ul nu este o rezervă din care provin alocările. Este transparent pentru aplicație – face doar ca restul memoriei RAM să pară mai rapidă în practică decât ar sugera latența sa brută, până în punctul în care un set de lucru nu mai încape în el.
Memoria procesorului strâns cuplată (tightly-coupled) – un bloc mic de RAM conectat direct la CPU, fără niciun magistral între ele. Acces într-un singur ciclu, fără rateuri, fără așteptări. Alocările care au cu adevărat nevoie de cea mai rapidă memorie posibilă – unde fiecare ciclu de latență contează – provin din această rezervă.
Memorie rapidă on-chip – de la câteva sute de kiloocteți până la aproximativ un megaoctet de RAM, integrată în pachetul MCU-ului. Latență scăzută, lățime de bandă mare, dar dimensiune limitată. Heap-ul MicroPython se află aici, astfel încât accesările obiectelor Python să rămână rapide; tampoanele de lucru mai mici, pe care CPU-ul le accesează frecvent, partajează aceeași rezervă.
Memorie de masă mai lentă – pe plăcile care asociază MCU-ul cu un cip de memorie extern, zeci de megaocteți de RAM off-chip, accesați prin magistrala externă. Mult mai mare, dar fiecare accesare durează mai mult decât la memoria on-chip; cache-ul de date ascunde o mare parte din acest cost pentru seturile de lucru pe care le poate reține, iar diferența apare la operațiile care parcurg date prea mari pentru a fi reținute în cache. Folosită pentru alocările care trebuie să fie mari și pe care CPU-ul le poate tolera la o viteză mai redusă – cel mai important, rezerva de tampoane de cadre.
Plăcile din familie se situează pe un spectru: unele au doar RAM on-chip; altele asociază RAM-ul on-chip cu un bloc extern mult mai mare. Fiecare dintre cele trei tipuri alocabile este tratat ca o rezervă de memorie – o porțiune din care provin alocările – și etichetat astfel încât fiecare cerere să poată solicita tipul de memorie de care are cu adevărat nevoie.
4.19.2. Tamponul de cadre principal¶
Tamponul de cadre (frame buffer) care susține snapshot() nu solicită memorie rapidă. Solicită suficientă memorie – nimic mai mult. Asta îl plasează în rezerva care este cea mai mare, astfel încât pe o placă cu memorie atât on-chip, cât și externă, tamponul de cadre ajunge în blocul extern.
Un tampon de cadre la rezoluție completă, cu triplă tamponare, este mult prea mare pentru a încăpea în rezerva rapidă on-chip pe majoritatea componentelor; rezerva mai mare este singura care îl poate găzdui în întregime. Cache-ul de date al CPU-ului ascunde o mare parte din costul fiecărei accesări atunci când aplicația procesează imaginea, iar motorul DMA care umple tamponul de cadre din senzor ține pasul cu rata de date a senzorului în ambele cazuri.
Dimensiunea exactă pe care o ocupă tamponul de cadre este aleasă în funcție de valorile curente ale pixformat(), framesize() și ale numărului framebuffers(); aceasta crește sau scade de fiecare dată când oricare dintre acestea se modifică.
4.19.3. Tampoane de cadre secundare ale senzorului¶
O a doua instanță CSI primește propriul tampon de cadre, alocat din aceeași rezervă pe care o folosește cel principal. Rezerva este partajată; tampoanele sunt independente. Amprenta celui secundar este în mod normal mult mai mică decât cea a celui principal, deoarece senzorii secundari rulează la rezoluții mai mici, astfel încât memoria suplimentară pe care o ocupă al doilea tampon de cadre reprezintă o mică fracțiune din cea a celui principal.
4.19.4. Tamponul de cadre pentru flux¶
Tamponul de previzualizare a imaginii reprezintă excepția. Acesta nu este alocat din niciuna dintre rezerve la momentul execuției; este o regiune fixă rezervată la momentul compilării, cu o adresă cunoscută și o dimensiune cunoscută. Asta menține calea de previzualizare în afara drumului oricărei alte alocări – regiunea există de la pornire și nu se mută niciodată.
4.19.5. Heap-ul MicroPython¶
Obiectele Python – variabile, liste, dicționare, instanțe de clasă, învelișul Image pe care îl returnează un apel snapshot(), fiecare șir și tuplu pe care le creează aplicația – se află pe heap-ul cu colectare a gunoiului din MicroPython, care este separat de rezervele de memorie ale camerei. Heap-ul cu colectare a gunoiului (GC) este o regiune de memorie pe care MicroPython o gestionează singur: codul Python alocă din el implicit de fiecare dată când este creat un obiect, iar MicroPython scanează periodic heap-ul și recuperează spațiul ocupat de obiectele pe care aplicația nu le mai referențiază, astfel încât aplicația nu trebuie niciodată să elibereze ceva manual.
O regiune dedicată este rezervată pentru heap-ul GC la pornire, plasată de obicei în memoria rapidă on-chip pentru ca accesul Python să rămână rapid, cu o revărsare opțională în blocul extern mai mare pe plăcile care au nevoie de mai mult spațiu pentru structuri de date mari.
Obiectul Image returnat de snapshot() este un mic obiect-înveliș aflat pe heap-ul GC; datele de pixeli subiacente se află în tamponul de cadre, într-una dintre rezervele camerei. Cele două nu concurează niciodată pentru aceeași memorie.
4.19.6. Punând totul cap la cap¶
Direcționarea fiecărui tip de alocare către rezerva potrivită – tampoanele mari către rezerva mai mare unde încap, datele sensibile la latență către rezervele mai rapide, heap-ul Python către propria regiune, previzualizarea către slotul său rezervat – este ceea ce face posibilă rularea simultană a unui flux complet de captură la rezoluție completă, a unui canal de previzualizare și a unui script Python netrivial pe componente care au în total doar câțiva megaocteți de memorie rapidă.