4.19. Geheugenpools¶
Een camera die drie frames op volledige resolutie vasthoudt in een framebufferpool, daarnaast een afzonderlijke previewbuffer draait en dan nog ruimte heeft voor een Python-script en zijn objecten, jongleert met meer geheugen dan een enkel blok RAM op de MCU zou kunnen leveren. MicroPython laat alles passen door het te verspreiden over de verschillende soorten geheugen die de MCU biedt, en door elke soort allocatie te routeren naar het soort geheugen dat ze daadwerkelijk nodig heeft.
4.19.1. Soorten geheugen¶
Een moderne OpenMV Cam-MCU stelt vier verschillende soorten geheugen beschikbaar. Het eerste is onzichtbaar voor de toepassing; de andere drie zijn pools waaruit allocaties kunnen komen.
De datacache van de CPU – een klein, zeer snel geheugengebied dat zich tussen de CPU en de rest van het RAM bevindt. Wanneer de CPU een waarde uit het hoofdgeheugen leest of schrijft, bewaart de cache automatisch een kopie, zodat herhaalde toegangen tot dezelfde gegevens in de cache blijven en nooit de kosten betalen van het uitwijken naar trager geheugen. De cache is geen pool waaruit allocaties komen. Ze is transparant voor de toepassing – ze laat de rest van het RAM in de praktijk gewoon sneller aanvoelen dan de ruwe latentie zou doen vermoeden, tot het punt waarop een werkverzameling er niet langer in past.
Nauw gekoppeld processorgeheugen – een klein blok RAM dat rechtstreeks aan de CPU is bedraad zonder bus ertussen. Toegang in één cyclus, nooit een misser, nooit wachten. Allocaties die echt het snelst mogelijke geheugen nodig hebben – waar elke cyclus latentie telt – komen uit deze pool.
Snel on-chipgeheugen – enkele honderden kilobytes tot ongeveer een megabyte RAM, ingebouwd in de MCU-behuizing. Lage latentie, hoge bandbreedte, maar beperkt in grootte. De MicroPython-heap bevindt zich hier zodat toegangen tot Python-objecten snel blijven; kleinere werkbuffers die de CPU veel aanraakt delen de pool.
Trager bulkgeheugen – op borden die de MCU koppelen aan een externe geheugen-die, tientallen megabytes off-chip RAM dat via de externe bus bereikt wordt. Veel groter, maar elke toegang duurt langer dan bij on-chipgeheugen; de datacache verbergt veel van die kosten voor werkverzamelingen die ze kan vasthouden, en het verschil komt aan het licht bij bewerkingen die over gegevens vegen die te groot zijn om te cachen. Wordt gebruikt voor allocaties die groot moeten zijn en die de CPU op tragere snelheid kan verdragen – het belangrijkste de framebufferpool.
Borden in de familie liggen op een spectrum: sommige hebben alleen on-chip RAM; sommige koppelen on-chip RAM aan een veel groter extern blok. Elk van de drie alloceerbare soorten wordt behandeld als een geheugenpool – een brok waaruit allocaties komen – en gelabeld zodat elke aanvraag kan vragen om het soort geheugen dat ze daadwerkelijk nodig heeft.
4.19.2. De primaire framebuffer¶
De framebuffer die snapshot() ondersteunt, vraagt niet om snel geheugen. Ze vraagt om voldoende geheugen – niets meer. Dat plaatst ze in welke pool dan ook de grootste is, dus op een bord met zowel on-chip- als extern geheugen komt de framebuffer terecht in het externe blok.
Een drievoudig gebufferde framebuffer op volledige resolutie is veel te groot om in de meeste onderdelen in de snelle on-chippool te passen; de grotere pool is de enige die ze überhaupt kan vasthouden. De datacache van de CPU verbergt veel van de kosten per toegang wanneer de toepassing de afbeelding verwerkt, en de DMA-engine die de framebuffer vult vanuit de sensor houdt hoe dan ook de gegevenssnelheid van de sensor bij.
De exacte grootte die de framebuffer inneemt wordt gekozen op basis van het huidige pixformat(), framesize() en het aantal framebuffers(); ze groeit of krimpt telkens wanneer een van die verandert.
4.19.3. Secundaire sensor-framebuffers¶
Een tweede CSI-instantie krijgt zijn eigen framebuffer, gealloceerd uit dezelfde pool die de primaire gebruikt. De pool wordt gedeeld; de buffers zijn onafhankelijk. De voetafdruk van de secundaire is normaal gesproken veel kleiner dan die van de primaire, omdat secundaire sensoren op lagere resoluties draaien, dus het extra geheugen dat de tweede framebuffer inneemt is een kleine fractie van dat van de primaire.
4.19.4. De stream-framebuffer¶
De afbeeldingspreviewbuffer is de uitzondering. Ze wordt tijdens runtime niet gealloceerd uit een van de pools; ze is een vast gebied dat tijdens het bouwen wordt gereserveerd, met een bekend adres en een bekende grootte. Dat houdt het previewpad uit de weg van elke andere allocatie – het gebied bestaat vanaf het opstarten en verplaatst nooit.
4.19.5. De MicroPython-heap¶
Python-objecten – variabelen, lijsten, dictionaries, klasse-instanties, de Image-wrapper die een snapshot()-aanroep retourneert, elke string en tuple die de toepassing aanmaakt – bevinden zich op de garbage-collected heap van MicroPython, die gescheiden is van de geheugenpools van de camera. De garbage-collected (GC) heap is een geheugengebied dat MicroPython zelf beheert: Python-code alloceert er impliciet uit telkens wanneer een object wordt aangemaakt, en MicroPython scant de heap periodiek en wint de ruimte terug die wordt ingenomen door objecten waarnaar de toepassing niet langer verwijst, zodat de toepassing nooit iets met de hand hoeft vrij te geven.
Bij het opstarten wordt een speciaal gebied gereserveerd voor de GC-heap, doorgaans geplaatst in snel on-chipgeheugen zodat Python-toegang snel blijft, met een optionele overloop naar het grotere externe blok op borden die meer speling nodig hebben voor grote gegevensstructuren.
De Image die door snapshot() wordt geretourneerd is een klein wrapper-object op de GC-heap; de onderliggende pixelgegevens bevinden zich in de framebuffer in een van de pools van de camera. De twee concurreren nooit om hetzelfde geheugen.
4.19.6. Alles samen¶
Het sturen van elke soort allocatie naar de juiste pool – grote buffers naar de grotere pool waar ze passen, latentiegevoelige gegevens naar de snellere pools, de Python-heap naar zijn eigen gebied, de preview naar zijn gereserveerde slot – is wat het mogelijk maakt om een opnamepijplijn op volledige resolutie, een previewkanaal en een niet-triviaal Python-script naast elkaar te draaien op onderdelen die in totaal slechts enkele megabytes snel geheugen hebben.