2.40. Minne och skräpsamling¶
MicroPython hanterar minne på samma sätt som CPython: varje objekt lever på heapen, och en skräpsamlare (GC) frigör objekt som ingenting längre refererar till. På en enhet med några hundra kilobyte RAM är det ibland nödvändigt att uppmärksamma hur heapen används; på desktop-Python är det sällan det.
2.40.1. Heapen¶
Heapen är den region av RAM – på de flesta OpenMV-kameror faktiskt uppdelad över mer än en fysisk minnespool – som körtiden delar ut till Python-objekt. Varje gång ett Python-uttryck skapar ett nytt objekt (en lista, en sträng, en dict, en tuple, allt som inte är ett litet heltal eller en singleton) tas ett block byte från heapen för att hålla det. När GC:n märker att ett objekt är onåbart returnerar den blocket till den fria poolen det kom ifrån.
Två funktioner i modulen gc är värda att känna till:
gc.mem_free()– ungefärligt antal lediga byte på heapen just nu.gc.collect()– kör en samlingscykel omedelbart istället för att vänta på att körtiden ska utlösa en.
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 exakta talen beror på bygget; det är riktningen på förändringen som spelar roll: att allokera stora saker minskar mem_free, och att släppa referenserna plus en gc.collect ger tillbaka heapen.
2.40.2. Fragmentering¶
Den fria poolen är inte magiskt sammanhängande. När objekt kommer och går med olika livslängder bryts det fria utrymmet upp i mindre och mindre delar även när dess totala storlek fortfarande är stor. Att allokera ett objekt större än den största enskilda fria delen misslyckas med MemoryError – även om det tekniskt sett finns tillräckligt med totalt fritt RAM.
Samma totala mängd fritt RAM kan rymma en stor buffert (vänster) eller vägra göra det (höger) beroende på hur fragmenterat det är.¶
Fragmentering är mest en oro på långkörande skript som hela tiden allokerar och frigör buffertar av olika storlek. Den enskilt effektivaste åtgärden är att förallokera långlivade buffertar nära programmets början, innan många kortlivade allokeringar har hunnit sprida ut dem.
2.40.3. Förallokering¶
Två mönster får heapen att uppföra sig:
Allokera buffertar med fast storlek en gång och återanvänd dem, istället för att bygga en ny lista eller
bytearrayper iteration.Lyft ut konstanter och uppslagstabeller ur inre loopar så att de skapas en gång.
buf = bytearray(64) # one allocation, reused below
def fill(value):
for i in range(len(buf)):
buf[i] = value
fill(0)
fill(255)
Jämför med en version som skapar en ny bytearray inuti loopen: varje iteration producerar skräp som GC:n måste städa upp senare. Den förallokerade versionen producerar inget skräp alls.
2.40.4. När man ska anropa gc.collect¶
gc.collect() är normalt automatisk – körtiden utlöser den när allokeringar inte kan hitta tillräckligt med ledigt minne. Att anropa den för hand är användbart i två situationer:
Direkt efter att en stor mängd objekt har gått ur scope, för att frigöra dem omedelbart istället för att vänta på att nästa allokering ska betala kostnaden.
Direkt före en sektion som behöver en känd maximal mängd ledigt minne, för att undvika att GC:n utlöses mitt i en tidskänslig operation.
Att strö gc.collect-anrop överallt gör inte ett program snabbare – samlingen i sig tar tid. Använd den medvetet, vid punkter där kostnaden för en oschemalagd samling skulle vara värre än kostnaden för en du körde med flit.