4.19. Pule pamięci¶
Kamera, która przechowuje trzy ramki w pełnej rozdzielczości w puli bufora ramki, jednocześnie obsługuje osobny bufor podglądu i mimo to ma jeszcze miejsce na skrypt Python oraz jego obiekty, żongluje większą ilością pamięci, niż mógłby zapewnić pojedynczy blok pamięci RAM w MCU. MicroPython mieści wszystko, rozprowadzając to po kilku odrębnych rodzajach pamięci, które udostępnia MCU, oraz kierując każdy rodzaj alokacji do tego rodzaju pamięci, którego faktycznie potrzebuje.
4.19.1. Rodzaje pamięci¶
Nowoczesny MCU w OpenMV Cam udostępnia cztery odrębne rodzaje pamięci. Pierwszy jest niewidoczny dla aplikacji; pozostałe trzy to pule, z których mogą pochodzić alokacje.
Pamięć podręczna danych CPU (data cache) – niewielki, bardzo szybki obszar pamięci znajdujący się pomiędzy CPU a resztą RAM. Gdy CPU odczytuje lub zapisuje wartość z pamięci głównej, pamięć podręczna automatycznie zachowuje jej kopię, dzięki czemu powtarzające się dostępy do tych samych danych pozostają w pamięci podręcznej i nigdy nie ponoszą kosztu sięgania do wolniejszej pamięci. Pamięć podręczna nie jest pulą, z której pochodzą alokacje. Jest przezroczysta dla aplikacji – po prostu sprawia, że reszta RAM w praktyce wydaje się szybsza, niż sugerowałoby jej surowe opóźnienie, aż do momentu, gdy zestaw roboczy przestaje się w niej mieścić.
Ściśle sprzężona pamięć procesora (tightly-coupled memory) – niewielki blok pamięci RAM podłączony bezpośrednio do CPU, bez magistrali pośredniej. Dostęp w jednym cyklu, nigdy nie chybia, nigdy nie czeka. Alokacje, które rzeczywiście wymagają najszybszej możliwej pamięci – gdzie liczy się każdy cykl opóźnienia – pochodzą z tej puli.
Szybka pamięć wbudowana (on-chip) – od kilkuset kilobajtów do około megabajta pamięci RAM wbudowanej w obudowę MCU. Niskie opóźnienie, wysoka przepustowość, ale ograniczony rozmiar. Tutaj znajduje się sterta MicroPython, dzięki czemu dostęp do obiektów Pythona pozostaje szybki; mniejsze bufory robocze, których CPU dotyka bardzo często, współdzielą tę pulę.
Wolniejsza pamięć masowa – na płytkach, które łączą MCU z zewnętrznym układem pamięci, dziesiątki megabajtów pamięci RAM poza układem, dostępnej przez zewnętrzną magistralę. Znacznie większa, ale każdy dostęp trwa dłużej niż w pamięci wbudowanej; pamięć podręczna danych ukrywa dużą część tego kosztu dla zestawów roboczych, które jest w stanie pomieścić, a różnica ujawnia się przy operacjach przebiegających przez dane zbyt duże, by je zbuforować. Używana do alokacji, które muszą być duże i które CPU może tolerować przy niższej szybkości – przede wszystkim do puli bufora ramki.
Płytki z tej rodziny mieszczą się na spektrum: niektóre mają tylko pamięć RAM wbudowaną; inne łączą pamięć RAM wbudowaną ze znacznie większym blokiem zewnętrznym. Każdy z trzech alokowalnych rodzajów jest traktowany jako pula pamięci – fragment, z którego pochodzą alokacje – i oznaczony, dzięki czemu każde żądanie może poprosić o ten rodzaj pamięci, którego faktycznie potrzebuje.
4.19.2. Podstawowy bufor ramki¶
Bufor ramki obsługujący snapshot() nie prosi o szybką pamięć. Prosi o wystarczającą ilość pamięci – nic więcej. Umieszcza go to w tej puli, która jest największa, więc na płytce z pamięcią wbudowaną i zewnętrzną bufor ramki trafia do bloku zewnętrznego.
Bufor ramki w pełnej rozdzielczości, potrójnie buforowany, jest na większości układów zdecydowanie zbyt duży, by zmieścić się w szybkiej puli wbudowanej; jedynie większa pula może go w ogóle pomieścić. Pamięć podręczna danych CPU ukrywa dużą część kosztu każdego dostępu, gdy aplikacja przetwarza obraz, a silnik DMA, który wypełnia bufor ramki danymi z sensora, i tak nadąża za szybkością danych sensora.
Dokładny rozmiar, jaki zajmuje bufor ramki, jest dobierany na podstawie bieżących wartości pixformat(), framesize() oraz liczby framebuffers(); rośnie lub maleje za każdym razem, gdy któraś z tych wartości się zmienia.
4.19.3. Dodatkowe bufory ramki sensorów¶
Druga instancja CSI otrzymuje własny bufor ramki, alokowany z tej samej puli, której używa instancja podstawowa. Pula jest współdzielona; bufory są niezależne. Zajętość pamięci przez instancję dodatkową jest zwykle znacznie mniejsza niż przez podstawową, ponieważ dodatkowe sensory pracują w niższych rozdzielczościach, więc dodatkowa pamięć zajmowana przez drugi bufor ramki to niewielki ułamek pamięci podstawowej.
4.19.4. Bufor ramki strumienia¶
Bufor podglądu obrazu jest wyjątkiem. Nie jest alokowany z żadnej z pul w czasie działania; jest stałym obszarem zarezerwowanym podczas kompilacji, o znanym adresie i znanym rozmiarze. Dzięki temu ścieżka podglądu nie wchodzi w drogę żadnej innej alokacji – obszar istnieje od momentu uruchomienia i nigdy się nie przemieszcza.
4.19.5. Sterta MicroPython¶
Obiekty Pythona – zmienne, listy, słowniki, instancje klas, obiekt opakowujący Image zwracany przez wywołanie snapshot(), każdy łańcuch znaków i krotka tworzone przez aplikację – znajdują się na stercie MicroPython odśmiecanej przez garbage collector, która jest oddzielona od pul pamięci kamery. Sterta odśmiecana (GC) to obszar pamięci, którym MicroPython zarządza samodzielnie: kod Pythona alokuje z niej niejawnie za każdym razem, gdy tworzony jest obiekt, a MicroPython okresowo skanuje stertę i odzyskuje miejsce zajmowane przez obiekty, do których aplikacja już się nie odwołuje, dzięki czemu aplikacja nigdy nie musi niczego zwalniać ręcznie.
Podczas uruchamiania dla sterty GC zostaje wydzielony dedykowany obszar, zwykle umieszczany w szybkiej pamięci wbudowanej, aby dostęp z Pythona pozostawał szybki, z opcjonalnym przepełnieniem do większego bloku zewnętrznego na płytkach, które potrzebują więcej zapasu dla dużych struktur danych.
Obiekt Image zwracany przez snapshot() jest małym obiektem opakowującym na stercie GC; leżące u jego podstaw dane pikseli znajdują się w buforze ramki w jednej z pul kamery. Te dwa elementy nigdy nie rywalizują o tę samą pamięć.
4.19.6. Wszystko razem¶
Kierowanie każdego rodzaju alokacji do właściwej puli – dużych buforów do większej puli, w której się mieszczą, danych wrażliwych na opóźnienie do szybszych pul, sterty Pythona do jej własnego obszaru, podglądu do zarezerwowanego dla niego miejsca – jest tym, co umożliwia jednoczesne uruchomienie potoku przechwytywania w pełnej rozdzielczości, kanału podglądu oraz nietrywialnego skryptu Python na układach, które łącznie mają tylko kilka megabajtów szybkiej pamięci.