A quanto sembra nell'ultimo Workshop c'è stato molto interesse sulla parte in cui parlavo di memoria. Allora ho pensato di riscrivere nel blog alcuni di quei concetti e magari di espanderne altri per cui non c'è stato il tempo.
In ogni post, se necessario, anticiperò qualche definizione... lungi da me darne una versione enciclopedica, per quelle ci sono libri mastri quali "Windows Internals" di Solomon-Russinovich e "Advanced Windows" di Richter a cui rimando per ogni approfondimento. Lo scopo di queste definizioni è solo per non perdere il filo del discorso da parte di chi non è familiare con questi termini.
- In Windows ogni processo definisce uno spazio di indirizzamento virtuale al di fuori del quale il codice non può andare a scrivere. Vale a dire che un puntatore, per quanto bizzarro valore possa assumere non potrà uscire da questo spazio di indirizzamento, e quindi non sovrascrivere la memoria di altri processi a meno che il processo non condivida delle pagine di memoria con altri.
- Lo spazio di indirizzamento dipende dall'architettura della CPU e nel caso di 32bit è 2^32 cioè 4GB di Ram indirizzabile da ogni processo. Questo spazio è normalmente diviso in due parti 2GB in User mode (la porzione dove vengono caricate le applicazioni) e 2GB in Kernel mode (dove gira il sistema operativo, i driver, etc.). Esiste un opzione del boot.ini per cui la divisione può essere di 3GB in User mode e 1GB in Kernel mode
- Ogni processo ha associato almeno un thread. Il thread è l'unità di esecuzione senza il quale il codice non può eseguire codice. Lo scheduler di Windows è preposto a decidere quanto tempo dare a ciascun thread del sistema in modo che l'esecuzione di tutti i processi sia il più omogenea possibile, in relazione anche alle priorità assegnate ai thread.
- Cos'è il rebasing? Ogni volta che un processo carica un modulo in memoria cerca di caricarlo all'indirizzo specificato dal developer. Quando l'indirizzo di memoria è già occupato, Windows deve rilocare (ribasare) la DLL in memoria aggiustando naturalmente tutti gli indirizzi di memoria della DLL. I problemi del rebasing sono molteplici e se interessa, dedicherò un post a questo argomento.
- Cos'è il paging su disco? Per far fronte alla necessità di memoria di tutti i processi, Windows ogni tanto scarica alcune pagine in RAM su disco, permettendo ad altri processi di usare RAM. Tipicamente più è usata la RAM fisica e migliore è la performance generale del sistema.
- Cosa sono i page fault? Quando un processo richiede della memoria e questa non è subito disponibile, viene scatenato un page fault. Se la pagina in questione è nel file di paging su disco fisso, il page fault è detto "hard page fault". Maggiori sono gli "hard page fault" e minore è la performance generale del sistema. I "soft page fault" sono invece generati se la pagina non è stata trovata ma è già in memoria e tipicamente non sono responsabili di grosse perdite di performance.
Veniamo ora alla memoria...
Perché ci dobbiamo preoccupare dell'occupazione di memoria? La domanda sembra scontata ma in fondo per ogni domanda, anche scontata, ci devono essere motivazioni oggettive.
- Perché la RAM è una risorsa relativamente limitata e ha un costo abbastanza alto rapportato all'uso che attualmente ne facciamo
- Perché la mancanza di RAM abbatte le performance del PC
- Perché nelle architetture a 32bit la memoria per ogni processo ha un limite che si può raggiungere senza troppa difficoltà
A questo punto diventa necessario misurare l'occupazione di memoria della nostra applicazione. Dimentichiamoci per il momento che l'applicazione sia managed, ossia che usi il Framework.NET. Parliamo generalmente di processi.
Lo strumento più usato è il task manager e la colonna che per default mostra un valore di memoria è "Mem Usage". Il corrispettivo valore in Process Explorer è "Working Set".
- Cosa rappresenta "Mem Usage"?
È il "working set" cioè la somma di pagine di memoria private + pagine che il processo condivide con altri processi. Sono tutte pagine in RAM fisica, cioè non su disco.
- Perché "Mem Usage" non è un'informazione utile?
Le pagine di memoria condivise sono contate per ciascun processo. Per cui la somma di tutti i "Mem Usage" di tutti i processi è superiore alla memoria realmente occupata.
- Quando accade che un processo condivida le sue pagine?
Ad esempio la porzione read-only delle DLL che vengono caricate in un processo e non sono ribasate dal loader vengono condivise, permettendo al sistema di occupare meno memoria.
Considerato che esistono sempre più applicazioni managed e che quindi è sempre più probabile che su un PC ce ne sia una attiva, certamente le System.*.dll saranno condivise tra tutti i processi managed. Lo stesso vale per le dll delle "C Runtime Library", delle MFC, ATL, VB e ovviamente quelle di sistema.
Detto questo, una applicazione managed che ha un alto "Mem Usage" potrebbe anche significare che usa pochissima memoria e fa un alto uso di DLL già in uso da altri processi.
Essendo una somma, l'analisi di solo questo valore è insufficiente a trarre risultati, ma se nel sistema sono presenti altre applicazioni managed, l'occupazione delle dll del Framework.net va suddiviso tra tutte queste.
Ovviamente esistono altri sistemi per avere informazioni più precise, e saranno oggetto dei prossimi post.