Lezione 2: Comprendere la gestione della memoria e la garbage collection in Python

CORSO PYTHON

La gestione della memoria è una componente cruciale di qualsiasi linguaggio di programmazione. In Python, questo tema assume una rilevanza particolare dati i suoi obiettivi di facilità d’uso e produttività. A differenza di linguaggi come C o C++, dove i programmatori sono responsabili diretti dell’allocazione e della deallocazione della memoria, Python offre una gestione automatizzata della memoria attraverso il garbage collector (GC). In questa lezione, esploreremo i meccanismi sottostanti, focalizzandoci sulla gestione della memoria e la funzionalità di garbage collection in Python.

Python utilizza un modello di memoria dinamica, basato su due componenti principali: il memory manager e il garbage collector. Il memory manager gestisce l’allocazione della memoria per gli oggetti creati in tempo di esecuzione, utilizzando una serie di gestori di memoria interni, tra cui il object-specific allocator e il Python’s own private heap space. La gestione efficiente della memoria è fondamentale per prevenire problemi come il memory leak o l’esaurimento della memoria disponibile.

Il garbage collector di Python, appartenente alla libreria gc  merita particolare attenzione, basato su un algoritmo di riferimento ciclico,  monitora gli oggetti referenziati per determinare quali possono essere deallocati. Nel dettaglio, Python utilizza un conteggio dei riferimenti (reference counting) combinato con il garbage collector per liberare la memoria non più utilizzata.

Un esempio di un semplice ciclo di riferimenti può essere creato in questo modo:

class Node:
    def __init__(self, value):
        self.value = value
        self.reference = None

# Creazione di un ciclo di riferimenti
a = Node(1)
b = Node(2)
a.reference = b
b.reference = a

In questo scenario, anche se le variabili a e b non esistono più nel codice, i loro riferimenti reciproci impediscono l’eliminazione automatica, causando un memory leak.  Il garbage collector di Python è progettato per rilevare questa situazione e risolverla efficacemente.

Come funziona la garbage collection in Python

La garbage collection (GC) in Python è un processo importante, spesso trascurato, che garantisce la gestione efficiente della memoria, introdotto già nelle versioni iniziali di Python, il sistema di garbage collection rimuove gli oggetti non più utilizzati dal programma, riducendo il rischio di “memory leak” e migliorando le prestazioni complessive. Comprendere come funziona questo meccanismo è cruciale per ogni sviluppatore Python, soprattutto quando si trattano applicazioni ad alta intensità di memoria.

Il suo funzionamento si basa principalmente sull’utilizzo del counting reference e della gestione delle “ciclomatiche” presenti nella memoria. Ogni oggetto in Python mantiene un contatore di riferimenti, denominato “reference count”. Quando un nuovo riferimento viene assegnato a un oggetto, il suo contatore aumenta; quando un riferimento viene eliminato o cambiato, il contatore diminuisce. Quando il contatore di un oggetto raggiunge lo zero, significa che l’oggetto non è più accessibile e può essere rimosso dalla memoria.

Ad esempio, consideriamo il codice seguente:

x = [1, 2, 3]  # Creare una lista
y = x          # Aumentare il reference count di x
del x          # Decrementare il reference count di x

In questo caso, l’oggetto lista iniziale ha un contatore di riferimento che passa da 1 a 2 quando viene assegnato a y e torna a 1 quando x viene eliminato. L’oggetto in memoria non verrà deallocato fino a quando tutti i riferimenti non saranno eliminati, ossia fino a quando y non verrà anch’esso eliminato.

Il sistema di garbage collection di Python accoppia questo modello di contatori di riferimento con un rilevatore di cicli. Le strutture dati cicliche, dove ad esempio due oggetti si riferiscono reciprocamente, possono risultare problematiche per un sistema basato esclusivamente sui contatori di riferimento. Per risolvere questo problema, Python utilizza il modulo gc che analizza periodicamente la memoria alla ricerca di gruppi di oggetti che si riferiscono l’un l’altro senza che ci siano riferimenti esterni. Quando tali cicli vengono trovati e verificati che non sono più accessibili dal programma, gli oggetti coinvolti possono essere correttamente deallocati.

Il modulo gc permette anche di gestire manualmente il processo di garbage collection. Funzioni come gc.collect() possono essere utilizzate per forzare la raccolta dei “rifiuti”, offrendo ai programmatori un maggiore controllo sulla gestione della memoria. Il modulo fornisce, inoltre,  utili strumenti di debugging per monitorare quali oggetti sono ancora in memoria e quali non lo dovrebbero essere, ciò può essere particolarmente utile durante lo sviluppo di applicazioni complesse.

Approfondimento del Reference counting e generational garbage collection

Reference counting è una tecnica in cui ogni oggetto mantiene un conteggio del numero di riferimenti ad esso. Quando questo conteggio scende a zero, l’oggetto viene considerato non più utilizzato e la memoria occupata da esso può essere liberata. E’ importante ricordare che Il reference counting non è in grado di gestire le strutture di dati con riferimenti circolari; ad esempio, due oggetti che fanno riferimento l’uno all’altro non avranno mai un conteggio dei riferimenti pari a zero, anche se non sono più accessibili dal programma principale.

Per risolvere questo problema, Python utilizza una garbage collection generazionale. Questo approccio si basa sull’osservazione che la maggior parte degli oggetti vivono per un breve periodo di tempo. Per ottimizzare la raccolta della memoria, gli oggetti vengono divisi in tre generazioni. Gli oggetti appena creati appartengono alla prima generazione. Quando questi oggetti sopravvivono a un certo numero di fasi di garbage collection, vengono spostati nella generazione successiva. Poiché le generazioni successive vengono scansionate meno frequentemente della prima, questo permette di ridurre il tempo complessivo dedicato alla garbage collection.

Ottimizzare l’uso della memoria in Python richiede una buona comprensione di questi meccanismi. Evitare, infatti, con un corretta programmazione  la creazione di riferimenti circolari inutili può ridurre la frequenza e l’onere della garbage collection;  A tal proposito è utile sapere che Python offre memorie specializzate come le deque e i set che possono essere più adatte rispetto alle liste o ai dizionari in certi contesti.

La gestione della memoria e la garbage collection in Python sono aspetti  che ogni sviluppatore dovrebbe comprendere a fondo. Attraverso una conoscenza approfondita e l’adozione di pratiche di codifica ottimizzate, è possibile migliorare significativamente le prestazioni dei propri programmi Python. Questo non solo si traduce in un software più veloce ed efficiente, ma può anche ridurre il consumo di risorse su larga scala, rendendo le applicazioni Python più sostenibili e scalabili.

Esempi pratici di gestione della memoria

Un esempio pratico di gestione della memoria potrebbe essere osservato in un contesto di manipolazione di dati di grandi dimensioni, come l’elaborazione di grandi database o l’analisi di dati scientifici. Supponiamo di avere un grande database di immagini che deve essere processato. Allocare correttamente la memoria per le immagini caricate può consumare una quantità significativa di risorse. In questi casi, i programmatore possono utilizzare librerie  come NumPy, che permette una gestione più efficiente della memoria grazie ai suoi array multidimensionali e che evita la creazione di numerosi piccoli oggetti, riducendo così il carico sulla garbage collection.

Un altro esempio potrebbe  riguardare l’uso del modulo weakref, che permette ai programmatori di creare riferimenti ‘deboli’ agli oggetti. Un riferimento debole non incrementa il contatore di riferimento dell’oggetto a cui punta, permettendo così alla garbage collection di deallocare l’oggetto se non ci sono altri riferimenti ‘forti’ che lo puntano. Questo è particolarmente utile nelle situazioni in cui si vogliono evitare cicli di riferimento non necessari.

<< Lezione 1: Introduzione ai concetti avanzati di Python

Lezione 3: Tecniche avanzate di manipolazione delle stringhe >>

Se vuoi farmi qualche richiesta o contattarmi per un aiuto riempi il seguente form