În aparență, Python gestionează memoria în mod complet automat: creezi variabile, iar interpretorul se ocupă de alocare și eliberare. Cu toate acestea, în servicii backend care rulează continuu timp de săptămâni (lucrători Celery, API-uri FastAPI, microservicii de procesare date), scurgerile de memorie (Memory Leaks) și fragmentarea pot duce la consumarea treptată a întregii memorii RAM a serverului și la oprirea forțată a procesului de către Linux OOM Killer (Out-of-Memory Killer).
# În CPython, pentru obiecte mici (<= 512 octeți), interpretorul NU apelează funcția malloc()
# din C la fiecare obiect, deoarece apelurile de sistem către OS ar cauza fragmentare masivă.
# În schimb, folosește alocatorul ierarhic PyMalloc:
# - Arena: Un bloc mare de memorie de 1 MB alocat din sistemul de operare.
# - Pool: O arenă este împărțită în pool-uri de 4 KB (corespunzătoare unei pagini virtuale).
# - Block: Fiecare pool este împărțit în blocuri de dimensiuni fixe (8, 16, 24 ... 512 octeți).
# De reținut: CPython NU returnează întotdeauna memoria fizică către sistemul de operare imediat!
# Blocurile eliberate sunt păstrate în arena internă pentru a fi reutilizate la următoarele alocări.
Depanarea Scurgerilor de Memorie cu tracemalloc
Modulul standard tracemalloc este cel mai puternic instrument integrat pentru identificarea exactă a liniei de cod care alocă memorie și nu o mai eliberează:
import tracemalloc
# 1. Pornim urmărirea alocărilor de memorie:
tracemalloc.start()
# Luăm primul snapshot de referință (baseline):
snapshot_initial = tracemalloc.take_snapshot()
# Simulare operațiune cu scurgere de memorie (de exemplu, un cache global care crește la infinit):
cache_global = []
for i in range(100_000):
cache_global.append(f"sesiune_utilizator_{i}_{i * 2}")
# Luăm al doilea snapshot după rularea sarcinii:
snapshot_dupa = tracemalloc.take_snapshot()
# Comparăm cele două stări și afișăm primele 3 linii de cod care au alocat memorie nouă:
statistici = snapshot_dupa.compare_to(snapshot_initial, 'lineno')
print("--- TOP ALOCĂRI NOI DE MEMORIE ---")
for stat in statistici[:3]:
print(stat)
# Exemplu output:
# /app/worker.py:12: size=8540 KiB (+8540 KiB), count=100000 (+100000)
Controlul Garbage Collector-ului (Modulul gc)
Reference counting-ul dealocă instantaneu memoria când ob_refcnt == 0. Singurul rol al colectorului ciclic (Cyclic GC) este identificarea referințelor circulare (A referă B, iar B referă A):
gc.collect(): Forțează o rundă completă de curățare pe toate cele 3 generații. Util de apelat la finalul unor joburi batch masive.gc.disable(): În medii cu performanță critică (de exemplu worker-e web gunicorn/uwsgi cu pre-forking), dezactivarea temporară a GC-ului înainte de fork și activarea ulterioară economisește memorie prin prevenirea spargerii paginilor partajate (Copy-on-Write).
Profilarea CPU cu cProfile și pstats
Pentru a afla unde își petrece timpul procesorul fără a ghici:
import cProfile
import pstats
def functie_complexa():
return [i ** 2 for i in range(2_000_000)]
# Rulăm profilul:
profiler = cProfile.Profile()
profiler.enable()
functie_complexa()
profiler.disable()
# Formatăm și afișăm raportul ordonat după timpul total petrecut (tottime):
statistici = pstats.Stats(profiler).sort_stats('tottime')
statistici.print_stats(5)