Posted by Dibi Store
Wed, 07 May 2008 13:12:00 GMT
Il modello sostitutivo, in ambito informatico, è il più semplice di una serie incrementale di modelli che descrivono come un interprete analizza il programma. L'uso del modello sostitutivo è quotidianamente usato anche dagli utenti per capire come una determinata procedura funziona.
Per cogliere al volo il concetto analizziamo il seguente listato:
pi: 3.14159
circonferenza(x): x * x * pi
somma_di_circonferenze(x, y): circonferenza(x) + circonferenza(y)
somma_di_circonferenze(4, 7) = ?
Read more...
Posted in programmazione | no comments | no trackbacks
Posted by Dibi Store
Sat, 29 Mar 2008 22:11:00 GMT
Quando programmiamo in alcuni linguaggi di alto livello, come ad esempio in ruby, non abbiamo bisogno di preoccuparci di come viene gestita la memoria da parte del computer. Tuttavia è interessante (oltre che utile) conoscere almeno a livello superficiale cosa avviene dietro le quinte. Questo articolo ha l'obiettivo di descrivere in maniera generale il processo di lettura e scrittura della memoria.
Registri
I registri sono la parte più performante della memoria, ma sono anche molto costosi, infatti nei processori 80x86, abbiamo a disposizione solo 8 registri da 32 bit (più altri di minore capacità). Quando lavoriamo a basso livello ci conviene sempre utilizzare i registri (se possibile) in modo di sfruttare appieno la velocità della nostra cpu.
Cache
La cache è quella parte della memoria che memorizza i valori corrispondenti a diversi indirizzi fisici di memoria. In ogni computer può esser presente uno o più livelli di cache, ad esempio L1 e L2. Prendendo come esempio L1, esso è costituito da diverse cache lines (linee di cache) che a loro volta contengono delle sequenze di byte.
In che modo vengono memorizzati i dati in cache?
Solitamente vengono usate tre strategie per determinare in quale cache line inserire il valore presente in memoria:
- strategia a mappatura diretta: da ogni indirizzo di memoria viene ricavata la relativa linea di cache attraverso un algoritmo. Tuttavia essendo le linee di cache un numero molto inferiore rispetto agli indirizzi di memoria, è probabile che si creino dei conflitti qualora si tenti di sovrascrivere una linea di cache già usata in precedenza.
- strategia a mappatura associativa: in questo caso il controller di cache, ha la possibilità di decidere arbitrariamente quale linea di cache utilizzare. Questo metodo è poco usato in quanto molto dispendioso a livelli di performance.
- memoria associativa n-volte (n-way set associative cache): In questo caso il blocco di cache che abbiamo a disposizione viene suddiviso n volte (per esempio 2 o 4), e queste suddivisioni vengono chiamate set. Questo ci permette di usare la strategia a mappatura diretta direttamente sui set, mentre al loro interno possiamo usare la strategia a mappatura associativa.
E quando tentiamo di sovrascrivere una linea di cache utilizzata in precedenza?
Nel caso in cui tentiamo di sovrascrivere una porzione di memoria attraverso una mappatura diretta, viene semplicemente dovrascritta la parte interessata, mentre nel caso stiamo usando una mappatura associativa, il discorso diventa più complesso: in alcuni casi viene sostituita la linea di cache usata meno recentemente, mentre altre volte la sostituzione avviene in modo randomico oppure attraverso il meccanismo fifo (first in first out).
Cosa succede quando scriviamo dati in memoria principale?
Ma soprattutto: quando tento di salvare un dato in un indirizzo di memoria, il livello di cache che si occupa di memorizzare il valore, come interagisce con questo processo?
Anche qui le scelte più usate sono due:
- write through policy
- write back policy
Write through policy; ogni volta che memorizziamo un dato in memoria, dapprima viene salvato in cache, subito dopo nella memoria principale. Write back policy invece salva prima il dato in memoria, e dopo un certo lasso di tempo lo salva nella memoria principale. Qualora stiamo salvando grosse quantità di memoria, nel primo caso il BUS che trasporta i dati dalla cache alla memoria principale finisce per intasarsi, analogamente nel secondo caso se tentiamo di memorizzare un dato nella stessa linea di cache, dobbiamo attendere che venga trasferito il rispettivo valore nella memoria principale. Nel caso la performance sia un elemento essenziale, è compito del programmatore (nei limiti del possibile) assicurarsi di utilizzare la cache in maniera propria.
NUMA
Questo livello di memoria viene usato generalmente da dispositivi quali schede video, schede di interfaccia, ecc, che ovviamente hanno un tempo di accesso maggiore rispetto alla memoria principale.
Memoria virtuale
La memoria virtuale simula la memoria principale utilizzando lo spazio su disco ed è responsabile del trasferimento di dati dall'hard disk alla memoria principale. E' inoltre responsabile della gestione dei processi del sistema, ad esempio ipotizzando di avere in attivo 10 servizi contemporaneamente, come può la CPU decidere quale memoria far utilizzare a ciascun servizio? La risposta è il paging.
Il paging consiste nel suddividere la memoria in porzioni (per esempio da 32 bit) le quali vengono affidate ai vari processi. In questo modo ogni processo avrà i suoi indirizzi di memoria riservati (nota che per il processo 1, l'indirizzo $F è diverso dal rispettivo $F nel processo 2). La CPU userà poi in seguito una tabella di lookup per conoscere l'effettivo indirizzo, che a sua volta contiene un meccanismo di cache per evitare problemi di performance.
Esempio pratico per l'ottimizzazione del software in C
int arr[123][123]
...
for(i = 0; i < 123; i++) arr[0][0] = ..
{ arr[1][0] = ..
for(j = 0; j < 123; j++) arr[2][0] = ...
{
arr[j][i] = i + j;
}
}
vs
int arr[123][123]
...
for(i = 0; i < 123; i++) arr[0][0] = ..
{ arr[0][1] = ..
for(j = 0; j < 123; j++) arr[0][2] = ...
{
arr[i][j] = i + j;
}
}
In questi due esempi l'unica differenza risiede nell'ultima istruzione. Il C usa l'ordinamento a colonne per gestire gli array di dimensione multipla, per cui vengono salvati gli indici in posizioni continue nella cache di memoria. Tuttavia nel primo esempio l'accesso avviene in modo non naturale, causando un fenomeno descritto come trashing, mentre nel secondo caso sfruttiamo appieno le capacità della CPU.
La chiave qui sta nel fatto che è sempre meglio studiare come avviene l'accesso alla memoria qualora si stia tentando di ottimizzare il proprio codice a questi livelli (anche se sarebbe sempre da prendere come regola generale).
Conclusioni
Nella lista non è stata inclusa la gestione della memoria su disco fisso, dispositivi esterni e network.
Approfondimenti:
Posted in programmazione | Tags memory | no comments | no trackbacks