2.40. Paměť a uvolňování paměti (garbage collection)#

MicroPython spravuje paměť stejným způsobem jako CPython: každý objekt žije na haldě (heap) a garbage collector (GC) uvolňuje objekty, na které už nic neodkazuje. Na zařízení s několika sty kilobajty RAM je občas nutné věnovat pozornost tomu, jak se halda využívá; na desktopovém Pythonu to nutné bývá jen zřídka.

2.40.1. Halda#

Halda je oblast RAM – na většině kamer OpenMV ve skutečnosti rozdělená napříč více než jedním fyzickým paměťovým fondem – kterou běhové prostředí přiděluje objektům Pythonu. Pokaždé, když výraz v Pythonu vytvoří nový objekt (seznam, řetězec, slovník, n-tici, cokoli, co není malé celé číslo nebo singleton), z haldy se vyčlení blok bajtů, který jej pojme. Když GC zjistí, že objekt je nedosažitelný, vrátí blok do volného fondu, ze kterého pocházel.

Za poznání stojí dvě funkce v modulu gc:

  • gc.mem_free() – přibližný počet volných bajtů na haldě v daném okamžiku.

  • gc.collect() – okamžitě spustí cyklus uvolňování paměti místo čekání, až jej běhové prostředí samo vyvolá.

import gc

print("before:", gc.mem_free())
big = [0] * 10000
print("after :", gc.mem_free())
del big
gc.collect()
print("freed :", gc.mem_free())

Přesná čísla závisí na sestavení; podstatný je směr změny: alokace velkých věcí snižuje mem_free a zahození odkazů spolu s gc.collect haldu vrátí.

2.40.2. Fragmentace#

Volný fond není zázračně souvislý. Jak objekty vznikají a zanikají s různou životností, volné místo se tříští na stále menší úseky, i když jeho celková velikost je stále velká. Alokace objektu většího než největší jednotlivý volný úsek selže s MemoryError – přestože je technicky vzato dostatek celkové volné RAM.

Two views of the heap. Before: one large contiguous free area. After: many small free areas interleaved with allocated blocks, none individually large enough for a big allocation.

Stejné celkové množství volné RAM může pojmout velký buffer (vlevo), nebo jej odmítnout (vpravo) podle toho, jak je fragmentované.#

Fragmentace je převážně starostí u dlouhoběžících skriptů, které neustále alokují a uvolňují buffery různých velikostí. Jediným nejúčinnějším opatřením je předem alokovat dlouho žijící buffery blízko začátku programu, dříve než je stihne rozházet množství krátkodobých alokací.

2.40.3. Předběžná alokace#

Haldu zkrotí dva postupy:

  • Alokujte buffery pevné velikosti jednou a znovu je používejte, místo abyste v každé iteraci vytvářeli nový seznam nebo bytearray.

  • Vytáhněte konstanty a vyhledávací tabulky z vnitřních cyklů, aby se vytvořily jen jednou.

buf = bytearray(64)        # one allocation, reused below

def fill(value):
    for i in range(len(buf)):
        buf[i] = value

fill(0)
fill(255)

Porovnejte to s verzí, která vytváří nové bytearray uvnitř cyklu: každá iterace produkuje odpad, který musí GC později uklidit. Předem alokovaná verze nevytváří žádný odpad.

2.40.4. Kdy volat gc.collect#

gc.collect() je normálně automatický – běhové prostředí jej spustí, když alokace nemohou najít dostatek volné paměti. Volat jej ručně je užitečné ve dvou situacích:

  • Hned poté, co velká dávka objektů opustila platnost, aby se uvolnily okamžitě, místo čekání, až cenu zaplatí příští alokace.

  • Těsně před částí, která potřebuje známé maximální množství volné paměti, aby se zabránilo spuštění GC uprostřed časově citlivé operace.

Rozsypávání volání gc.collect všude program nezrychlí – samotné uvolňování zabere čas. Používejte jej uvážlivě, na místech, kde by cena neplánovaného uvolnění byla horší než cena toho, které jste spustili záměrně.