25

Feb

CLR Memory Management

 

ciao a tutti

 

nel .NET esistono due categorie di tipi (TYPE in originale): Value Type e Reference Type

questa la loro suddivisione:

  • VALUE TYPES
    • INT (short, long, uint, ushort, byte, sbyte, decimal, etc)
    • FLOAT (double, float)
    • BOOL
    • ENUM (enumerazioni)
    • STRUCT (strutture)
  • REFERENCE TYPE
    • Object (e ogni derivato)
    • STRING

 

 

Nel .NET framework esistono due principali aree di memoria in cui vengono conservati gli oggetti durante l’esecuzione (run-time), Heap e Stack

Come in molti libri è enunciato lo Stack è l’area che conserva i value-type, mentre lo Heap è l’area che conserva i reference-type (le istanze delle classi nostre o del framework stesso)

Ovviamente, questa è una “barzelletta”…. nel senso che non è esattamente così – benché possa sembrare

 

 

 

Nel CLR (common-language-runtime) del .NET framework, implementato da Microsoft (in pratica la virtual machine in cui il codice .NET si converte da IL – il precompilato – a binario), esistono 3 aree di memoria in cui le nostre variabili di vario tipo vengono memorizzate durante l’esecuzione (heap, stack & registers). L’unico vero discriminante nella scelta della memoria da utilizzare, è lo scope (la prevista durata come compresa dal compilatore o dal Runtime) della nostra variabile

 

Questo già mette in luce che esiste un’area di memoria di solito dimenticata (Registers), così come mette in luce il fatto che in realtà MONO (il CLR per Linux e non solo) potrebbe avere una architettura della gestione della memoria completamente diversa (non so se mono si comporti diversamente o egualmente al clr Ms)

 

Ulteriore appunto alla definizione usuale è il fatto che in realtà non è il tipo di variabile a caratterizzarne l’allocazione in memoria, ma la sua dichiarazione, e più precisamente il suo presunto ciclo di vita (life-time in originale)

Un esempio tipico è il fatto che gli INT dentro una variabile di tipo Reference, non sono sullo Stack ma sullo Heap

 

 

La spiegazione sul perché avviene tutto questo è la seguente:

Se dichiariamo una variabile di tipo primitivo, e la utilizziamo in maniera procedurale, il CLR sa esattamente quando verrà dichiarata, e quando verrà abbandonata, quindi la può caricare nello Stack o nel Register

Mentre se è una istanza di classe, che appunto è un Reference Type, questo rende più difficile al CLR capire quando esattamente potrebbe essere abbandonato l’uso della variabile (cosa che infatti impatta in seguito il GC), e quindi il CLR adottando un approccio conservativo, mette il Reference (il riferimento della variabile – diciamo pure il puntatore) nello Stack, e il contenuto della variabile puntata nello Heap, proprio perché considera long-lived l’oggetto…. in pratica dice: io non riesco a predire se sarà ad uso breve o lungo, quindi lo metto nell’area di memoria degli oggetti a vita lunga, se poi sarà breve, il GC lo rimuoverà

 

Ulteriore esempi di oggetti nello Heap (long-lived memory area) sono le variabili cross-utilizzate nelle lambda, queste appunto essendo dei Delegate, ed essendo soggetti all’Azione a Distanza per flusso di esecuzione ed anche per distanza temporale, vanno automaticamente nello Heap anche se normalmente di tipo short-lived, ad esempio un INT che viene passato come parametro non diretto all’interno di una lambda (in pratica quelli fuori alla Action/Func, ma che vengono visualizzati anche dal codice anonimo della funzione, perché nello stesso scope della lambda stessa).

 

BOXING/UNBOXING:

Ovviamente l’operazione di boxing (inserimento di un valore primitivo in un oggetto contenitore – in genere Object) rende il tipo un Reference type, e quindi automaticamente soggetto al problema già detto del passaggio dell’istanza da short-lived a long-lived, e quindi al passaggio da Stack/Register all’Heap, con conseguente perdita di performance dovuta alla copia del valore tra 1 area di memoria ad 1 altra.

 

Simile il comportamento che avviene quando utilizziamo una lambda: come già detto questa rende tutte le variabili dello stesso scope automaticamente long-lived, quindi al pari di un’operazione di boxing

 

CONCLUSIONI:

Attenzione a quello che si scrive… molte applicazioni legacy, vedi i gestionali Client-Server, sono molto poco ottimizzati nell’uso delle variabili, lasciando spesso al CLR il compito di “arrangiarsi”. Anche se nel tempo ci si è basati sul fatto che tanto ogni utente avrà il proprio PC e che difficilmente il rallentamento sarà generale e quindi problematico, questo cambia con il cambiare delle architetture software che vanno sempre più verso N-Tier, Servizi/SOA e Cloud Computing.

 

Inoltre va detto che, realizzando applicazioni che vanno spesso sul Cloud, dove la bassa ottimizzazione la paghiamo noi in bolletta, è meglio fare attenzione a quello che facciamo ed a come lo facciamo.

 

spero di essere stato chiaro, almeno un po Sorriso

 

 

 

 

RIFERIMENTI:

Eric Lippert’s Blobg

by Antonio Esposito on 2/25/2013