4.19. Minnespooler

En kamera som håller tre bildrutor i full upplösning i en bildbuffertpool, kör en separat förhandsgranskningsbuffert vid sidan av och fortfarande har plats för ett Python-skript och dess objekt jonglerar med mer minne än vad ett enda block av RAM på MCU:n skulle kunna tillhandahålla. MicroPython får plats med allt genom att sprida ut det över de flera olika typer av minne som MCU:n erbjuder, och genom att dirigera varje slags allokering till den typ av minne den faktiskt behöver.

4.19.1. Typer av minne

En modern OpenMV Cam-MCU exponerar fyra olika typer av minne. Den första är osynlig för applikationen; de andra tre är pooler som allokeringar kan komma ifrån.

  • CPU:ns datacache – en liten, mycket snabb minnesregion som sitter mellan CPU:n och resten av RAM. När CPU:n läser eller skriver ett värde från huvudminnet behåller cachen automatiskt en kopia, så att upprepade åtkomster till samma data stannar i cachen och aldrig betalar kostnaden för att gå ut till långsammare minne. Cachen är inte en pool som allokeringar kommer ifrån. Den är transparent för applikationen – den får helt enkelt resten av RAM att kännas snabbare i praktiken än dess råa latens skulle antyda, ända tills en arbetsmängd slutar få plats i den.

  • Tätt kopplat processorminne – ett litet block av RAM kopplat direkt till CPU:n utan någon buss emellan. Åtkomst på en enda cykel, missar aldrig, väntar aldrig. Allokeringar som genuint behöver det snabbast möjliga minnet – där varje cykel av latens spelar roll – kommer ur denna pool.

  • Snabbt minne på chipet – några hundra kilobyte upp till ungefär en megabyte RAM, inbyggt i MCU-kapseln. Låg latens, hög bandbredd, men begränsat i storlek. MicroPython-heapen bor här så att åtkomster till Python-objekt förblir snabba; mindre arbetsbuffertar som CPU:n rör vid ofta delar poolen.

  • Långsammare bulkminne – på kort som parar MCU:n med en extern minneskrets, tiotals megabyte RAM utanför chipet som nås över den externa bussen. Mycket större, men varje åtkomst tar längre tid än minne på chipet; datacachen döljer mycket av den kostnaden för arbetsmängder den kan hålla, och gapet visar sig vid operationer som sveper över data som är för stor för att cacha. Används för allokeringar som måste vara stora och som CPU:n kan tolerera i lägre hastighet – viktigast av allt, bildbuffertpoolen.

Kort i familjen faller på ett spektrum: vissa har bara RAM på chipet; vissa parar RAM på chipet med ett mycket större externt block. Var och en av de tre allokerbara typerna behandlas som en minnespool – en bit som allokeringar kommer ur – och märks så att varje begäran kan be om den typ av minne den faktiskt behöver.

4.19.2. Den primära bildbufferten

Bildbufferten som ligger bakom snapshot() ber inte om snabbt minne. Den ber om tillräckligt med minne – inget mer. Det placerar den i den pool som är störst, så på ett kort med både minne på chipet och externt minne hamnar bildbufferten i det externa blocket.

En trippelbuffrad bildbuffert i full upplösning är alldeles för stor för att få plats i den snabba poolen på chipet på de flesta komponenter; den större poolen är den enda som kan hålla den över huvud taget. CPU:ns datacache döljer mycket av kostnaden per åtkomst när applikationen bearbetar bilden, och DMA-motorn som fyller bildbufferten från sensorn håller jämna steg med sensorns datahastighet i vilket fall som helst.

Den exakta storlek bildbufferten tar väljs utifrån aktuella pixformat(), framesize() och framebuffers()-antal; den växer eller krymper varje gång någon av dessa ändras.

4.19.3. Sekundära sensorbildbuffertar

En andra CSI-instans får sin egen bildbuffert, allokerad från samma pool som den primära använder. Poolen delas; buffertarna är oberoende. Den sekundäras fotavtryck är normalt mycket mindre än den primäras, eftersom sekundära sensorer kör i lägre upplösningar, så det extra minne den andra bildbufferten tar är en liten bråkdel av den primäras.

4.19.4. Strömbildbufferten

Bufferten för bildförhandsgranskning är undantaget. Den allokeras inte från någon av poolerna vid körning; den är en fast region reserverad vid bygget, med en känd adress och en känd storlek. Det håller förhandsgranskningsvägen ur vägen för varje annan allokering – regionen finns från uppstart och flyttar sig aldrig.

4.19.5. MicroPython-heapen

Python-objekt – variabler, listor, ordböcker, klassinstanser, Image-omslaget som ett snapshot()-anrop returnerar, varje sträng och tupel applikationen skapar – bor på den sopuppsamlade MicroPython-heapen, som är skild från kamerans minnespooler. Den sopuppsamlade (GC) heapen är en minnesregion som MicroPython själv hanterar: Python-kod allokerar från den implicit varje gång ett objekt skapas, och MicroPython skannar regelbundet heapen och återvinner utrymmet som tas av objekt applikationen inte längre refererar till, så att applikationen aldrig behöver frigöra något för hand.

En dedikerad region avsätts för GC-heapen vid uppstart, vanligtvis placerad i snabbt minne på chipet så att Python-åtkomst förblir snabb, med ett valfritt överflöde in i det större externa blocket på kort som behöver mer marginal för stora datastrukturer.

Image som returneras av snapshot() är ett litet omslagsobjekt på GC-heapen; den underliggande pixeldatan bor i bildbufferten i en av kamerans pooler. De två konkurrerar aldrig om samma minne.

4.19.6. Sätta ihop det

Att styra varje slags allokering till rätt pool – stora buffertar till den större poolen där de får plats, latenskänslig data till de snabbare poolerna, Python-heapen till sin egen region, förhandsgranskningen till sin reserverade plats – är vad som gör det möjligt att köra en bildtagningspipeline i full upplösning, en förhandsgranskningskanal och ett icke-trivialt Python-skript bredvid varandra på komponenter som bara har några få megabyte snabbt minne totalt.