2.40. Memorija i sakupljanje smeća#

MicroPython upravlja memorijom na isti način kao CPython: svaki objekt živi na heapu, a sakupljač smeća (GC) oslobađa objekte koje više ništa ne referencira. Na uređaju s nekoliko stotina kilobajta RAM-a, obraćanje pažnje na to kako se heap koristi povremeno je nužno; na stolnom Pythonu to rijetko jest.

2.40.1. Heap#

Heap je područje RAM-a – na većini OpenMV kamera zapravo podijeljeno na više od jednog fizičkog memorijskog spremišta – koje radno okruženje dodjeljuje Python objektima. Svaki put kada Python izraz stvori novi objekt (popis, niz, rječnik, n-torku, bilo što što nije mali cijeli broj ili singleton), blok bajtova izlazi iz heapa da bi ga sadržao. Kada GC primijeti da je objekt nedostupan, vraća blok u slobodno spremište iz kojeg je došao.

Vrijedi poznavati dvije funkcije u modulu gc:

  • gc.mem_free() – približan broj slobodnih bajtova na heapu upravo sada.

  • gc.collect() – pokrenite ciklus sakupljanja odmah umjesto da čekate da ga radno okruženje aktivira.

import gc

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

Točni brojevi ovise o verziji; smjer promjene je ono što je važno: alociranje velikih stvari smanjuje mem_free, a ispuštanje referenci uz gc.collect vraća heap.

2.40.2. Fragmentacija#

Slobodno spremište nije čarobno neprekinuto. Kako objekti dolaze i odlaze s različitim vijekom trajanja, slobodni prostor se lomi na sve manje i manje komade čak i kada je njegova ukupna veličina i dalje velika. Alociranje objekta većeg od najvećeg pojedinačnog slobodnog komada ne uspijeva uz MemoryError – iako tehnički postoji dovoljno ukupnog slobodnog RAM-a.

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.

Isti ukupni slobodni RAM može primiti veliki međuspremnik (lijevo) ili to odbiti (desno) ovisno o tome koliko je fragmentiran.#

Fragmentacija je uglavnom briga kod dugotrajnih skripti koje neprestano alociraju i oslobađaju međuspremnike različitih veličina. Najučinkovitija mjera ublažavanja jest unaprijed alocirati dugovječne međuspremnike blizu početka programa, prije nego što je mnoštvo kratkovječnih alokacija imalo priliku raspršiti ih.

2.40.3. Unaprijedna alokacija#

Dva obrasca čine da se heap dobro ponaša:

  • Alocirajte međuspremnike fiksne veličine jednom i ponovno ih koristite, umjesto da gradite novi popis ili bytearray po iteraciji.

  • Izvucite konstante i tablice za pretraživanje iz unutarnjih petlji tako da se stvore jednom.

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

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

fill(0)
fill(255)

Usporedite s verzijom koja stvara novi bytearray unutar petlje: svaka iteracija proizvodi smeće koje GC kasnije mora počistiti. Unaprijed alocirana verzija ne stvara nikakvo smeće.

2.40.4. Kada pozvati gc.collect#

gc.collect() normalno je automatska – radno okruženje je aktivira kada alokacije ne mogu pronaći dovoljno slobodne memorije. Ručno pozivanje korisno je u dvije situacije:

  • Odmah nakon što je velika serija objekata izašla iz dosega, da bi se oslobodili odmah umjesto da se čeka da sljedeća alokacija plati tu cijenu.

  • Neposredno prije dijela koji treba poznatu maksimalnu količinu slobodne memorije, kako bi se izbjeglo aktiviranje GC-a usred vremenski osjetljive operacije.

Posipanje poziva gc.collect posvuda ne čini program bržim – samo sakupljanje oduzima vrijeme. Koristite ga promišljeno, na točkama gdje bi cijena neplaniranog sakupljanja bila gora od cijene onog koje ste pokrenuli namjerno.