2.40. Geheugen en garbage collection¶
MicroPython beheert geheugen op dezelfde manier als CPython: elk object leeft op de heap, en een garbage collector (GC) maakt objecten vrij waarnaar niets meer verwijst. Op een apparaat met een paar honderd kilobyte RAM is het af en toe nodig om aandacht te besteden aan hoe de heap wordt gebruikt; op desktop-Python is dat zelden zo.
2.40.1. De heap¶
De heap is het gebied van het RAM – op de meeste OpenMV-camera’s eigenlijk verdeeld over meer dan één fysieke geheugenpool – dat de runtime uitdeelt aan Python-objecten. Telkens wanneer een Python-expressie een nieuw object aanmaakt (een list, een string, een dict, een tuple, alles wat geen klein geheel getal of singleton is), komt er een blok bytes uit de heap om het op te slaan. Wanneer de GC merkt dat een object onbereikbaar is, geeft hij het blok terug aan de vrije pool waar het vandaan kwam.
Twee functies in de gc-module zijn het kennen waard:
gc.mem_free()– het bij benadering aantal vrije bytes op de heap op dit moment.gc.collect()– voer onmiddellijk een collectiecyclus uit in plaats van te wachten tot de runtime er een triggert.
import gc
print("before:", gc.mem_free())
big = [0] * 10000
print("after :", gc.mem_free())
del big
gc.collect()
print("freed :", gc.mem_free())
De exacte getallen hangen af van de build; de richting van de verandering is wat ertoe doet: het alloceren van grote dingen verlaagt mem_free, en het laten vallen van de referenties plus een gc.collect geeft de heap terug.
2.40.2. Fragmentatie¶
De vrije pool is niet op magische wijze aaneengesloten. Naarmate objecten met verschillende levensduren komen en gaan, breekt de vrije ruimte in steeds kleinere stukken, zelfs wanneer de totale omvang nog steeds groot is. Het alloceren van een object dat groter is dan het grootste enkele vrije stuk faalt met MemoryError – ook al is er technisch gezien genoeg totaal vrij RAM.
Hetzelfde totale vrije RAM kan een grote buffer bevatten (links) of dat weigeren (rechts), afhankelijk van hoe gefragmenteerd het is.¶
Fragmentatie is vooral een zorg bij langdurig draaiende scripts die buffers van verschillende grootte blijven alloceren en vrijgeven. De meest effectieve maatregel is om langlevende buffers vooraf te alloceren aan het begin van het programma, voordat veel kortlevende allocaties de kans hebben gehad om ze te verstrooien.
2.40.3. Pre-allocatie¶
Twee patronen zorgen ervoor dat de heap zich netjes gedraagt:
Alloceer buffers van vaste grootte één keer en hergebruik ze, in plaats van per iteratie een nieuwe list of
bytearrayop te bouwen.Haal constanten en opzoektabellen uit binnenlussen zodat ze één keer worden aangemaakt.
buf = bytearray(64) # one allocation, reused below
def fill(value):
for i in range(len(buf)):
buf[i] = value
fill(0)
fill(255)
Vergelijk met een versie die binnen de lus een nieuwe bytearray aanmaakt: elke iteratie produceert garbage die de GC later moet opruimen. De vooraf gealloceerde versie maakt helemaal geen garbage.
2.40.4. Wanneer gc.collect aan te roepen¶
gc.collect() gebeurt normaal gesproken automatisch – de runtime triggert hem wanneer allocaties niet genoeg vrij geheugen kunnen vinden. Hem met de hand aanroepen is nuttig in twee situaties:
Direct nadat een grote batch objecten buiten scope is geraakt, om ze onmiddellijk vrij te maken in plaats van te wachten tot de volgende allocatie de kost betaalt.
Direct voor een sectie die een bekende maximale hoeveelheid vrij geheugen nodig heeft, om te voorkomen dat de GC halverwege een tijdgevoelige operatie afgaat.
Overal gc.collect-aanroepen strooien maakt een programma niet sneller – de collectie zelf kost tijd. Gebruik het bewust, op punten waar de kost van een ongeplande collectie erger zou zijn dan de kost van een die je doelbewust hebt uitgevoerd.