2.40. Memória és szemétgyűjtés¶
A MicroPython ugyanúgy kezeli a memóriát, mint a CPython: minden objektum a heap-en él, és egy szemétgyűjtő (GC) felszabadítja azokat az objektumokat, amelyekre már semmi sem hivatkozik. Egy néhány száz kilobájt RAM-mal rendelkező eszközön időnként szükséges figyelmet fordítani arra, hogyan használjuk a heap-et; asztali Python-on erre ritkán van szükség.
2.40.1. A heap¶
A heap a RAM azon területe – a legtöbb OpenMV kamerán valójában több fizikai memóriakészlet között elosztva –, amelyet a futtatókörnyezet a Python objektumoknak oszt ki. Valahányszor egy Python kifejezés új objektumot hoz létre (listát, karakterláncot, dict-et, tuple-t, bármit, ami nem kis egész szám vagy szingleton), egy bájtblokk kerül ki a heap-ből, hogy tárolja azt. Amikor a GC észreveszi, hogy egy objektum elérhetetlen, visszaadja a blokkot abba a szabad készletbe, ahonnan származott.
A gc modul két függvényét érdemes ismerni:
gc.mem_free()– a heap-en éppen szabad bájtok hozzávetőleges száma.gc.collect()– azonnal lefuttat egy gyűjtési ciklust, ahelyett, hogy megvárná, amíg a futtatókörnyezet kiváltja egyet.
import gc
print("before:", gc.mem_free())
big = [0] * 10000
print("after :", gc.mem_free())
del big
gc.collect()
print("freed :", gc.mem_free())
A pontos számok a buildtől függenek; a változás iránya az, ami számít: nagy dolgok lefoglalása csökkenti a mem_free értéket, a hivatkozások elejtése plusz egy gc.collect pedig visszaadja a heap-et.
2.40.2. Töredezettség¶
A szabad készlet nem varázslatos módon összefüggő. Ahogy az objektumok különböző élettartamok mellett jönnek és mennek, a szabad terület egyre kisebb és kisebb darabokra törik, még akkor is, ha teljes mérete továbbra is nagy. Egy a legnagyobb egyetlen szabad darabnál nagyobb objektum lefoglalása MemoryError hibával bukik el – noha technikailag elegendő összesen szabad RAM van.
Ugyanaz az összesen szabad RAM be tud fogadni egy nagy puffert (balra), vagy elutasítja azt (jobbra) attól függően, mennyire töredezett.¶
A töredezettség többnyire hosszan futó szkripteknél jelent gondot, amelyek folyamatosan különböző méretű puffereket foglalnak le és szabadítanak fel. A leghatékonyabb enyhítés az, ha a hosszú életű puffereket előre lefoglaljuk a program elejéhez közel, mielőtt rengeteg rövid életű lefoglalásnak alkalma lett volna szétszórni őket.
2.40.3. Előre lefoglalás¶
Két minta szabályozott viselkedésre bírja a heap-et:
Foglalj le rögzített méretű puffereket egyszer, és használd újra őket, ahelyett, hogy minden iterációban új listát vagy
bytearrayobjektumot építenél.Húzd ki a konstansokat és a kereső táblákat a belső ciklusokból, hogy egyszer jöjjenek létre.
buf = bytearray(64) # one allocation, reused below
def fill(value):
for i in range(len(buf)):
buf[i] = value
fill(0)
fill(255)
Hasonlítsd össze egy olyan változattal, amely a cikluson belül hoz létre új bytearray-t: minden iteráció szemetet termel, amelyet a GC-nek később ki kell takarítania. Az előre lefoglalt változat egyáltalán nem termel szemetet.
2.40.4. Mikor hívjuk a gc.collect-ot¶
A gc.collect() rendszerint automatikus – a futtatókörnyezet akkor váltja ki, amikor a lefoglalások nem találnak elég szabad memóriát. Kézzel meghívni két helyzetben hasznos:
Közvetlenül azután, hogy egy nagy adag objektum hatókörén kívülre került, hogy azonnal felszabaduljanak, ahelyett, hogy megvárnánk, amíg a következő lefoglalás állja a költséget.
Közvetlenül egy olyan szakasz előtt, amelynek ismert maximális mennyiségű szabad memóriára van szüksége, hogy elkerüljük, hogy a GC egy időérzékeny művelet közepén induljon el.
Ha mindenhová gc.collect hívásokat szórsz, az nem teszi gyorsabbá a programot – maga a gyűjtés időbe telik. Használd átgondoltan, olyan pontokon, ahol egy nem ütemezett gyűjtés költsége rosszabb lenne, mint egy szándékosan lefuttatotté.