Appendice 4 - Siti personali

Transcript

Appendice 4 - Siti personali
Ap p u n t i d i Ca l co l a t o ri El et t ro n i ci
Appendice 4 – La memoria
L A MEMORIA CACHE ........................................................................................................... 1
Introduzione .................................................................................................................. 1
Il principio di localizzazione ........................................................................................... 2
Organizzazioni delle memorie cache ................................................................................ 3
Gestione delle scritture in una cache ............................................................................... 8
L A MEMORIA VIRTUALE ...................................................................................................... 9
Introduzione .................................................................................................................. 9
La paginazione............................................................................................................... 9
Meccanismi di paginazione ........................................................................................... 12
La paginazione su richiesta........................................................................................... 16
Politiche per la sostituzione delle pagine ....................................................................... 17
La dimensione delle pagine e la frammentazione ............................................................ 18
L
hee
orriiaa ccaacch
mo
meem
Laa m
Introduzione
Storicamente, le CPU sono state sempre più veloci delle memorie: quando le
memorie sono migliorate, lo hanno fatto anche le CPU, mantenendo la distanza. Ciò
comporta, in concreto, una conseguenza importante: dopo che la CPU ha emesso
una richiesta di memoria, deve rimanere inattiva per un tempo rilevante, aspettando
la risposta dalla memoria. Tipicamente, la richiesta della CPU avviene in un ciclo di
clock e poi devono passare due o tre cicli successivi prima di ottenere i dati. Questo
rallenta troppo l’esecuzione complessiva, per cui è il caso di porvi rimedio.
E’ importante capire che il “problema” è di natura economica e non tecnologia:
infatti, gli ingegneri sanno bene come costruire memorie veloci quanto le CPU, ma
sanno anche che esse sarebbero molto costose, tanto che dotare un calcolatore di
anche un solo megabyte di tali memorie sarebbe improponibile. Per questo motivo,
si è scelto di avere solo una piccola quantità di memoria veloce (eventualmente
espandibile a cura dell’utente) ed una grande quantità di memoria lenta.
Esistono varie tecniche per combinare una piccola quantità di memoria veloce
con una grande quantità di memoria lenta, in modo da ottenere, allo stesso tempo e
con un costo moderato, (quasi) la velocità di quella veloce e la capacità di quella
grande. La memoria piccola e veloce è detta cache (che deriva dal francese
“cacher”, ossia “nascondere”) ed è sotto il controllo del microprogramma. Vogliamo
allora descrivere come si usano e come funzionano le memorie cache.
Appunti di “Calcolatori Elettronici” – Appendice 4
Il principio di localizzazione
Ormai da diverso tempo è stato accertato che i programmi non accedono al loro
codice in modo completamente casuale: se capita un riferimento di memoria ad un
dato indirizzo A, è molto probabile che il riferimento successivo sia nelle vicinanze
di A. Ne è una prova lo stesso programma:
•
ad eccezione dei salti e delle chiamate di procedura, le istruzioni sono
prelevate da locazioni consecutive in memoria;
•
inoltre, la maggior parte del tempo di esecuzione di un programma viene
usata per i cicli, in cui quindi vengono eseguite un numero limitato di
istruzioni in maniera ripetitiva.
Questa osservazione per cui i riferimenti alla memoria, fatti in un intervallo
breve, tendono ad usare una piccola frazione della memoria totale è noto come
principio di localizzazione ed è alla base di tutti i sistemi di cache. L’idea generale
è la seguente: quando viene fatto un riferimento ad una parola, questa viene
trasferita dalla memoria grande e lenta nella cache, in modo da essere accessibile
velocemente per le eventuali e probabili successive utilizzazioni.
Una architettura molto usata della CPU, della cache e della memoria centrale è
illustrata nella figura seguente:
Piastra della CPU
Piastra di Memoria
CPU
Memoria piccola,
veloce e costosa
Memoria
centrale
Memoria grande,
lenta ed economica
Memoria
Cache
BUS
Architettura tipica di un calcolatore con memoria cache: quest’ultima è generalmente
localizzata sulla piastra della CPU
Per renderci conto dell’opportunità di usare una cache, possiamo fare il seguente
semplicissimo discorso: supponiamo che una parola venga letta o scritta k volte in
un breve intervallo, durante l’esecuzione di un dato programma; se tale parola si
trova inizialmente nella memoria centrale e, alla prima richiesta, viene portata
nella cache, il calcolatore avrà bisogno di un solo riferimento per la memoria lenta
(il primo) e k-1 riferimenti per la memoria veloce. Quanto maggiore è k (ossia,
sostanzialmente, quanto più risulta verificato il principio di localizzazione), tanto
migliori saranno le prestazioni globali, proprio perché tanto maggiore è il numero di
volte in cui viene coinvolta la memoria veloce al posto della memoria lenta.
Possiamo formalizzare ulteriormente questo calcolo, introducendo due tempi:
tempo di accesso alla cache (che indichiamo con C) e tempo di accesso alla
Autore: Sandro Petrizzelli
2
aggiornamento: 6 luglio 2001
Compendio sulla memoria: memoria cache e memoria virtuale
memoria centrale (che indichiamo con M). Sulla base di questi tempi, possiamo
calcolare il cosiddetto rapporto di successo (simbolo H), ossia la frazione di tutti i
riferimenti che si possono soddisfare con la cache: nell’esempio che abbiamo fatto
poco fa, risulta evidentemente
K −1
K
H=
Il duale di questa quantità è il cosiddetto rapporto di fallimento, pari
evidentemente a
1− H = 1−
K −1 1
=
K
K
Possiamo inoltre calcolare il tempo medio di accesso alla generica parola, nel
modo seguente:
tempo medio di accesso = C + (1 − H) × M = C +
M
K
Questa formula esprime chiaramente il fatto che il tempo medio di accesso è la
somma del tempo di accesso alla cache e del tempo di accesso alla memoria, pesato
però quest’ultimo per (1-H) o, ciò che è lo stesso, per 1/K. Di conseguenza, quanto
più H tende ad 1, tanto più i riferimenti a memoria possono essere soddisfatti dalla
cache e quindi il tempo totale di accesso si avvicina a quello della cache. Al
contrario, se H→0, è quasi sempre necessario un riferimento alla memoria centrale,
per cui il tempo di accesso si avvicina adesso a C+M: il tempo iniziale C serve a
controllare che la parola richiesta sia in cache e, una volta verificato che non è
così , serve un tempo M per accedere alla memoria centrale.
In alcuni sistemi, per velocizzare il funzionamento, si possono cominciare in
parallelo l’accesso alla memoria e la ricerca nella cache: in tal modo, se la ricerca
nella cache è infruttuosa, il ciclo di memoria è stato già attivato. Tuttavia, una
strategia di questo tipo richiede che si possa fermare la ricerca in memoria quando
si ha successo (in inglese hit) nella cache, il che rende l’hardware più complicato.
In ogni caso, l’algoritmo di base per cercare nella cache e cominciare (o fermare)
l’accesso alla memoria centrale, a seconda del risultato della ricerca nella cache, è
gestito dal microprogramma.
Organizzazioni delle memorie cache
Si usano due diverse organizzazioni fondamentali della cache, oltre ad una terza
che è una forma di “ibrido” delle prime due.
Supponiamo, per tutti i tre tipi, che la memoria principale sia di 2 m byte (dove m
è quindi la lunghezza degli indirizzi di memoria) e che essa sia divisa (solo
concettualmente) in blocchi consecutivi, ciascuno di b byte, per un totale
chiaramente di 2 m /b blocchi. Così facendo, ogni blocco ha un indirizzo che è un
multiplo di b (dove b è generalmente una potenza di 2).
Il primo tipo di cache è quella cosiddetta completamente associativa, composta
da un certo numero di slot (detti anche linee), ciascuno dei quali contiene tre
elementi: un blocco, il numero identificativo di tale blocco ed un bit (chiamato
Valid) che indica se lo slot è attualmente in uso oppure no. La figura seguente
illustra un esempio di questa organizzazione:
aggiornamento: 6 luglio 2001
3
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Appendice 4
Memoria principale
Numero
di blocco
Indirizzo
0
137
0
52
1
1410
2
635
3
4
8
12
Memoria cache
Valid
Numero di blocco
1
0
137
1
600
2131
1
2
1410
0
1
16
...
...
Valore
160248
90380
....
.....
22 bit
32 bit
1024
slot
0
..
...
224
224
1 bit
Esempio di schema di cache completamente associativa con 1024 slot (affiancata ad una
memoria centrale con blocchi da 4 byte)
A sinistra è riportato un “pezzo” di memoria principale, suddivisa in blocchi da 4
byte ciascuno; a destra è invece riportato un “pezzo” di una memoria cache
completamente associativa, in cui ogni slot ha la struttura descritta prima. Si
considera, in particolare, una cache con 1024 slot ed una memoria di 2 24 byte
divisi in 2 22 blocchi da 4 byte ciascuno. Nella cache completamente associativa,
l’ordine degli elementi è casuale.
Quando il calcolatore viene inizializzato, tutti i bit Valid sono posti a 0, in modo
da indicare che, al momento, non ci sono elementi della cache “validi”, cioè
utilizzati. Supponiamo adesso che la prima istruzione del programma in esecuzione
si riferisca alla parola di 32 bit situata all’indirizzo 0:
•
il microprogramma controlla tutti gli elementi della cache, cercandone uno
valido contenente il blocco numero 0;
•
non trovando il suddetto blocco (la cache è vuota), emette una richiesta sul
bus, per prelevare la parola 0 dalla memoria;
•
sistema quindi tale parola nel blocco 0 e rende valido quest’ultimo.
A questo punto, se la parola dovesse risultare nuovamente necessaria, potrà
essere prelevata direttamente dalla cache, eliminando la necessità di una
operazione sul bus.
Chiaramente, con il procedere dell’esecuzione, un numero sempre crescente di
elementi della cache risulteranno contrassegnati come validi. Nel caso in cui il
programma usi meno di 1024 parole per il programma e per i dati, si verifica la
situazione ideale, dato che tutto il programma ed i suoi dati appariranno alla fine
nella cache, per cui esso sarà eseguito ad alta velocità, senza bisogno di accessi
alla memoria centrale attraverso il bus. E’ chiaro, però, che questa è una situazione
poco realistica; è più probabile, invece, che il programma ed i suoi dati richiedano
più di 1024 parole: in questo caso, si arriverà ad un certo punto in cui tutta la
cache risulterà piena e bisognerà eliminare vecchi elementi per far spazio a quelli
nuovi. La decisione di quale elemento eliminare è di importanza critica e deve tra
l’altro essere presa molto velocemente (pochi nanosecondi): alcune macchine
Autore: Sandro Petrizzelli
4
aggiornamento: 6 luglio 2001
Compendio sulla memoria: memoria cache e memoria virtuale
prendono uno slot a caso, mentre invece generalmente sono usati algoritmi più
sofisticati.
La caratteristica importante della cache completamente associativa è che ogni
slot contiene il numero identificativo del blocco contenuto oltre al blocco stesso.
Quando si presenta un indirizzo di memoria, il microprogramma deve compiere
quindi due operazioni:
•
calcolare il numero del blocco corrispondente all’indirizzo specificato;
•
controllare l’elemento della cache che ha quel numero di blocco, se
presente.
Mentre la prima operazione è molto semplice, la seconda non lo è affatto. Per
evitare una ricerca sequenziale sulla cache, quest’ultima possiede un hardware
speciale, che confronta ogni elemento simultaneamente con il numero di blocco
calcolato. Questo evita di usare un ciclo del microprogramma, ma rende anche più
costosa la cache.
Per ridurre tale costo, fu allora inventato un diverso tipo di cache, detta a mappa
diretta (o anche ad indirizzamento diretto): in questo caso, si evita la fase di ricerca
del blocco, mettendo ogni blocco in uno slot il cui numero può essere facilmente
calcolato dal numero del blocco. In altre parole, dato un blocco, esiste un unico
slot della cache in cui può essere sistemato e quindi il controllo sulla presenza o
meno del blocco in cache andrà effettuato su quell’unico slot. Il numero di tale slot
può essere ad esempio calcolato nel modo seguente: si fa la divisione tra il numero
del blocco ed il numero degli slot e si considera il resto di tale divisione.
Brevemente, si scrive
(numero del blocco) modulo (numero di slot)
Ad esempio, con blocchi di 4 byte (cioè una parola) e con 1024 slot nella
memoria, il numero dello slot per la parola che si trova all’indirizzo A è
A
modulo 1024
4
dove ovviamente A/4 è il numero del blocco in cui si trova la parola il cui indirizzo
è A.
Nel nostro esempio, lo slot 0 della cache è destinato alle parole che hanno
indirizzo 0, 4096, 8192, 12288 e così via; lo slot 1 è invece destinato alle parole che
hanno indirizzo 4, 4100, 8196, 12292 e così via.
La cache a mappa diretta elimina dunque il problema della ricerca di un blocco,
ma crea un nuovo problema: bisogna infatti capire come indicare quale, delle
diverse parole che corrispondono ad un dato slot, lo stia effettivamente occupando.
Ad esempio, poco fa abbiamo detto che lo slot 0 può accogliere le parole ad
indirizzo 0, 4096, 8192, 12288 e così via. Il modo per indicare quale di queste sia
effettivamente presente nello slot è di porre una parte dell’indirizzo di tali parole
nella cache, introducendo un apposito campo Tag (indicatore): tale campo viene
così a contenere quella parte di indirizzo che non può essere calcolata partendo dal
numero dello slot.
Facciamo un esempio:
•
consideriamo una istruzione all’indirizzo 8192 e supponiamo che essa
sposti una parola dall’indirizzo 4100 all’indirizzo 12296;
aggiornamento: 6 luglio 2001
5
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Appendice 4
•
il numero del blocco corrispondente a 8192 si ottiene dividendo per 4
(ossia per la dimensione dei blocchi nel nostro esempio): si ottiene 2048;
•
dal numero del blocco possiamo poi risalire al numero dello slot che lo può
accogliere: facendo 2048 modulo 1024, si ottiene 0. Lo stesso risultato si
sarebbe ottenuto usando i 10 bit meno significativi del numero 2048, ossia
del numero del blocco. Essendo tale numero di 22 bit, i restanti 12 bit (che
in questo caso contengono il valore 2) andranno a costituire il tag.
Nella figura seguente è riportata la cache dopo che tutti e tre gli indirizzi sono
stati elaborati nel modo appena descritto:
Valid
Indicatore
Valore
0
1
2
12130
1
1
1
170
2
1
3
2142
3
0
4
0
5
0
..
..
....
.....
1023
1
Calcolo dello slot e dell'indicatore partendo da un indirizzo di 24 bit
Indicatore
Slot
00
Esempio di cache a mappa diretta, con 1024 slot di 4 byte ciascuno. Nella parte inferiore è
mostrato il procedimento di calcolo dello slot e dell’indicatore partendo da un indirizzo di 24 bit
Questa figura mostra anche (nella parte inferiore) come si divide l’indirizzo di
memoria (da 24 bit, avendo supposto una memoria da 2 24 byte):
•
i due bit meno significativi sono sempre a 0, dato che la cache lavora
sempre con blocchi interi e questi sono multipli della dimensione del
blocco, che nel nostro esempio è di 4 byte;
•
segue il numero di slot da 10 bit;
•
infine, il numero dell’indicatore (12 bit) preso direttamente dalla cache.
Una soluzione di questo tipo è anche facilmente implementabile, dato che si può
agevolmente costruire un hardware che estrae direttamente il numero dello slot e
dell’indicatore da qualunque indirizzo di memoria.
Il fatto che diversi blocchi di memoria possono far riferimento allo stesso slot
della cache può comunque provocare dei problemi. Ad esempio, supponiamo che
una istruzione di spostamento sposti una parola dall’indirizzo 4100 all’indirizzo
12292 (al posto di 12296 come supposto prima); entrambi questi indirizzi si
riferiscono allo slot 1: infatti, 12292/4 fornisce il numero di blocco 3073 e questo
numero, diviso per 1024 slot, fornisce un resto pari a 1. In questa situazione, a
seconda della microprogrammazione, l’ultimo calcolato finirà nella cache, mentre
l’altro verrà eliminato; non si tratta ovviamente di un evento disastroso, ma esso
Autore: Sandro Petrizzelli
6
aggiornamento: 6 luglio 2001
Compendio sulla memoria: memoria cache e memoria virtuale
contribuisce comunque a rallentare le prestazioni, tanto più quante più sono le
parole in uso che si riferiscono allo stesso slot.
Per ovviare a questo problema, si può adottare una estensione della cache a
mappa diretta, tesa a poter memorizzare più blocchi in ciascuno slot. Si ottiene
così la cache associativa ad insiemi, detta anche cache set-associativa ad N vie,
dove N sono i blocchi memorizzabili in ciascuno slot:
Slot Valid Indicatore
0
1
2
3
4
5
6
7
8
...
Valore
Valid Indicatore
Valore
Valid Indicatore
Valore
Esempio di cache set associativa a 3 vie (3 blocchi per ogni slot)
In effetti, sia la cache completamente associativa sia quella ad indirizzamento
diretto sono casi particolari della cache set-associativa:
•
se usiamo un unico slot, tutti gli elementi della cache si trovano nello
stesso slot e dobbiamo perciò distinguerli con indicatori, dato che fanno
riferimento allo stesso indirizzo. Otteniamo perciò nuovamente una cache
completamente associativa;
•
se invece prendiamo N=1 (1 blocco per ogni slot), allora abbiamo
nuovamente la cache ad indirizzamento diretto.
Dal punto di vista di vantaggi e vantaggi dei vari tipi di cache, possiamo citare i
seguenti:
•
le cache ad indirizzamento diretto sono semplici ed economiche da
costruire e presentano inoltre un tempo di accesso minore, dato che lo slot
corrispondente si può trovare semplicemente indicizzando la cache (usando
una porzione dell’indirizzo come indice);
•
al contrario, la cache associativa ha un numero maggiore di successi per
un dato numero di slot, in quanto non si verificano mai conflitti: non può
infatti succedere che k parole importanti non si possano mettere nella
cache contemporaneamente perché hanno la sfortuna di far riferimento
allo stesso slot.
Il progettista deve decidere non solo il numero degli slot, ma anche la dimensione
dei blocchi. Nei nostri esempi, abbiamo sempre usato blocchi da 4 byte, ma sono
usate anche altre dimensioni. Il vantaggio di usare una grande dimensione per i
blocchi è che risulta meno gravoso prelevare un blocco di 8 parole rispetto ad 8
blocchi ciascuno di 1 parola, specialmente se il bus permette il trasferimento di
blocchi. C’è però anche lo svantaggio che non tutte le parole di un dato blocco
possono essere necessarie, nel qual caso parte del prelevamento è sprecato.
aggiornamento: 6 luglio 2001
7
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Appendice 4
Gestione delle scritture in una cache
Un altro importante problema, nella progettazione di una cache, è quello della
gestione delle scritture. Si usano solitamente due strategie:
•
nella prima, detta scrittura in avanti, quando si scrive una parola nella
cache, la si registra immediatamente anche nella memoria. In tal modo, si
assicura che gli elementi della cache siano sempre uguali ai corrispondenti
elementi della memoria;
•
nella seconda, detta invece copia all’indietro, la memoria non viene
aggiornata tutte le volte che la cache viene alterata, ma solo quando
bisogna eliminare un elemento dalla cache per far posto ad un altro. In
questo caso, è chiaramente necessario un bit per ogni elemento della
cache, che indichi se l’elemento è stato modificato oppure no durante la
sua permanenza in cache: solo in caso affermativo, prima di eliminarlo
esso andrà registrato in memoria.
Ci sono una serie di conseguenze legate ad una ed all’altra scelta:
•
la scrittura in avanti provoca evidentemente un maggiore traffico nel bus
rispetto alla copia all’indietro;
•
viceversa, se una CPU comincia un trasferimento di I/O dalla memoria al
disco e la memoria non è “aggiornata”, allora dati non corretti saranno
scritti sul disco. E’ ovvio che una simile evenienza viene comunque sempre
evitata, al prezzo però di una maggiore complessità del sistema;
•
se il rapporto tra letture e scritture risulta molto alto, può essere più
conveniente l’uso della scrittura in avanti, tollerando il traffico sul bus,
che comunque subirebbe dei picchi saltuari (in occasione appunto delle
scritture). Se invece ci sono molte scritture, allora il traffico potrebbe
essere eccessivo e sarebbe perciò preferibile la scrittura all’indietro; non
solo, ma sarebbe anche opportuno che il microprogramma “pulisca” l’intera
cache (ossia la copi interamente nella memoria) prima di una eventuale
operazione di I/O.
Autore: Sandro Petrizzelli
8
aggiornamento: 6 luglio 2001
Compendio sulla memoria: memoria cache e memoria virtuale
L
Laa m
meem
mo
orriiaa vviirrttu
uaallee
Introduzione
All’inizio dell’era dei calcolatori, le memorie erano piccole e care; di
conseguenza, i programmatori passavano buona parte del loro tempo cercando di
comprimere i propri programmi in memorie piccolissime; spesso, era perfino
necessario usare un algoritmo che lavorava molto più lentamente di un altro,
semplicemente perché l’altro era troppo grande per essere conservato nella
memoria del calcolatore.
La soluzione tradizionale a questo problema fu, inizialmente, quella di servirsi,
oltre che della memoria principale, anche di una memoria secondaria, ad
esempio un disco:
•
il programmatore divideva il programma in diversi “pezzi”, chiamati
overlay, ognuno dei quali poteva stare in memoria;
•
per eseguire il programma, si inseriva il primo overlay e lo si eseguiva;
quando era finito, si caricava l’overlay successivo e lo si eseguiva e così
via.
Il problema era che, comunque, era sempre il programmatore a dover dividere il
programma in overlay e a decidere dove dovesse essere tenuto ogni overlay nella
memoria secondaria; il programmatore doveva anche curare gli spostamenti degli
overlay tra memoria secondaria e memoria principale, senza alcun aiuto da parte
del calcolatore.
E’ ovvio come questa tecnica richiedesse un lavoro eccessivo e noioso da parte
del programmatore, per cui, verso il 1960, fu ideato un metodo per l’esecuzione
automatica della gestione degli overlay, senza che il programmatore sapesse
neppure cosa stesse succedendo. Questo metodo, che ora prende il nome di
memoria virtuale, libera dunque il programmatore da un grosso lavoro di tipo
“amministrativo”. Al giorno d’oggi, praticamente tutti i microprocessori presentano
sistemi più o meno sofisticati di memoria virtuale.
La paginazione
L’idea alla base del metodo della memoria virtuale è quella di separare due
concetti: lo spazio di indirizzamento e le locazioni di memoria. Per spiegarci meglio,
utilizziamo un esempio concreto.
Consideriamo un calcolatore nelle cui istruzioni il campo di indirizzamento sia di
16 bit: questo significa che un programma eseguito su questo calcolatore può
indirizzare 2 16 =65536 parole di memoria (che costituiscono il cosiddetto spazio di
indirizzamento) e questo a prescindere dalla quantità di memoria a disposizione,
dato che il numero di parole indirizzabili dipende appunto dal numero di bit
utilizzabili per gli indirizzi e non dal numero di parole effettivamente disponibili.
Supponiamo, ad esempio, che il calcolatore disponga di una memoria di appena
4096 parole. Prima dell’invenzione della memoria virtuale, sarebbe stata necessaria
una distinzione, all’interno dello spazio di indirizzamento, tra gli indirizzi al di sotto
di 4096 (da 0 a 4095) e quelli uguali a superiori a 4096: i primi costituiscono lo
spazio di indirizzamento utile, dato che corrispondono a locazioni di memoria
aggiornamento: 6 luglio 2001
9
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Appendice 4
effettivamente disponibili, mentre gli altri costituiscono uno spazio
indirizzamento inutile, visto che non corrispondono a locazioni disponibili.
di
Spazio di indirizzamento
da 64 K
Indirizzi
0
Spazio di
indirizzamento
utile
4095
Memoria centrale
da 4 K
Spazio di
indirizzamento
inutile
65535
Suddivisione dello spazio di indirizzamento totale in parte “utile” e parte “inutile”
L’idea di separare lo spazio di indirizzamento e gli indirizzi di memoria è la
seguente:
•
avendo a disposizione 4096 parole di memoria effettiva, siamo certi che, in
qualunque istante, possiamo accedere a 4096 parole di memoria;
•
tuttavia,
non
necessariamente
queste
parole
effettive
devono
corrispondente agli indirizzi compresi tra 0 e 4095: ad esempio, potremmo
“imporre” al calcolatore che, da un certo momento in poi, tutte le volte che
si usa l’indirizzo 4096, deve essere usata la parola 0 della memoria;
quando si usa l’indirizzo 4097 dovrà essere usata la parola 1 della
memoria e così via, fino all’indirizzo 8191, al quale dovrà corrispondere la
parola 4095 della memoria.
Si realizza, perciò, una corrispondenza uno-ad-uno del tipo schematizzato nella
figura seguente:
Spazio di indirizzamento
da 64 K
Indirizzi
4096
Memoria centrale
da 4 K
8191
65535
Corrispondenza tra indirizzi dello spazio di indirizzamento e indirizzi della memoria centrale
Autore: Sandro Petrizzelli
10
aggiornamento: 6 luglio 2001
Compendio sulla memoria: memoria cache e memoria virtuale
In termini molto semplici, possiamo affermare che è stata definita una funzione
dallo spazio di indirizzamento sugli indirizzi di memoria effettivi: ogni indirizzo nello
spazio di indirizzamento viene “tradotto” in un corrispondente indirizzo fisico, ossia
indirizzo della memoria centrale effettivamente disponibile.
Supponendo dunque fissata questa particolare “funzione” (o “corrispondenza” o
“associazione”) tra indirizzi nello spazio di indirizzamento e indirizzi effettivi nella
memoria centrale, supponiamo che un dato programma “salti” ad un indirizzo (nello
spazio di indirizzamento) compreso tra 8192 e 12287. Che succede? Se la macchina
non è dotata di memoria virtuale, il programma provoca un errore e viene stampato
un messaggio del tipo “Riferimento a memoria inesistente”, dopodiché il programma
termina. Il motivo è evidente: il calcolatore non ha alcuna corrispondenza tra gli
indirizzi compresi tra 8192 e 12287 e gli indirizzi effettivi di memoria, per cui non
sa dove trovare i dati o le istruzioni richieste dal programma e quindi non può che
arrestarsi.
Introducendo, invece, la memoria virtuale, si avvia il seguente processo:
•
il contenuto della memoria centrale viene salvato in una memoria
secondaria (ad esempio un disco);
•
in questa stessa memoria secondaria vengono individuate le parole da
8192 a 12287;
•
le suddette parole vengono caricate nella memoria centrale;
•
la mappa degli indirizzi (ossia la predetta funzione che associa lo spazio di
indirizzamento agli indirizzi effettivi) viene modificata, al fine di far
corrispondente gli indirizzi da 8192 a 12287 alle locazioni della memoria
centrale da 0 a 4095;
•
infine, l’esecuzione continua come se non fosse successo niente di insolito.
In pratica, si è eseguito un qualcosa di simile ad uno “spostamento automatico di
overlay”. Questa tecnica prende il nome di paginazione ed i pezzi di programma
trasferiti dalla memoria secondaria in quella principale sono detti pagine.
In effetti, quella appena descritta è una versione particolarmente semplificata
della paginazione; è invece possibile un modo più sofisticato per far corrispondere
lo spazio di indirizzamento con gli indirizzi effettivi di memoria, così come vedremo
più avanti.
Chiariamoci dunque sulla terminologia da adottare:
•
gli indirizzi a cui il programma può fare riferimento costituiscono il
cosiddetto spazio di indirizzamento virtuale (di 64K nel caso di indirizzi
a 16 bit);
•
gli indirizzi di memoria effettivamente cablati costituiscono invece lo
spazio di indirizzamento fisico (di 4K nel nostro esempio);
•
la mappa di memoria è infine quella funzione che lega gli indirizzi virtuali
con quelli fisici.
Nei nostri discorsi, diamo ovviamente per scontato che la memoria secondaria
abbia abbastanza spazio per memorizzare l’intero programma ed i suoi dati.
Ogni programma viene scritto come se ci fosse abbastanza memoria centrale per
l’intero spazio di indirizzamento virtuale, anche se non è così : i programmi possono
quindi caricare o memorizzare una qualsiasi parola nello spazio di indirizzamento
virtuale, così come possono saltare ad una qualsiasi istruzione localizzata in un
aggiornamento: 6 luglio 2001
11
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Appendice 4
posto qualsiasi dello spazio di indirizzamento virtuale, senza preoccuparsi che, in
realtà, non c’è abbastanza memoria fisica. In poche parole, il programmatore può
scrivere programmi senza sapere se la memoria virtuale esiste, dando comunque
per scontato che il calcolatore possieda memoria sufficiente ( 1).
La paginazione dà al programmatore l’illusione di una grande memoria centrale,
della stessa dimensione dello spazio di indirizzamento, anche se in pratica la
memoria centrale disponibile può essere più piccola (o anche più grande) dello
spazio di indirizzamento. La “simulazione” di questa grande memoria centrale,
garantita dalla paginazione, è trasparente al programma (ed al programmatore), il
quale cioè non si accorge della sua presenza (a meno di non eseguire test
particolari): tutte le volte che si fa riferimento ad un indirizzo, la corrispondente
parola sembra comunque sempre presente.
Meccanismi di paginazione
Un requisito essenziale, per una memoria virtuale, è che il programma completo
da eseguire sia interamente contenuto nella memoria secondaria.
Anche se la realtà è quella per cui i “pezzi” di programma originale si trovano
nella memoria principale, mentre le “copie” si trovano nella memoria secondaria,
risulta comunque più semplice pensare che avvenga il viceversa, ossia che il
programma nella memoria secondaria sia l’originale, mentre i pezzi portati nella
memoria centrale di tanto in tanto siano delle copie. L’importante è tener presente
quello che avviene nella realtà e, soprattutto, mantenere sempre aggiornato
l’originale (in base all’esecuzione che se ne sta facendo): quando si operano dei
cambiamenti alla copia nella memoria centrale, questi devono essere riprodotti
anche nell’originale ( 2).
Dato lo spazio di indirizzamento virtuale, lo si divide in un certo numero di
pagine, tutte di uguale dimensione (che deve essere una potenza di 2). Ad esempio,
fino a poco tempo fa erano comuni pagine da 4096 indirizzi ciascuna. Nella
prossima figura, ad esempio, è riportata la suddivisione di uno spazio di
indirizzamento virtuale di 64K in 16 pagine (da 0 a 15) da 4K ciascuna e con 4096
indirizzi ciascuna.
Evidentemente, anche lo spazio di indirizzamento fisico (la memoria centrale)
viene diviso in modo simile, con ogni pezzo (detto page frame) che ha la stessa
dimensione di una pagina, in modo tale che ogni pezzo di memoria centrale sia
capace di contenere esattamente una pagina. In questo esempio, si considera in
particolare una memoria principale da 32K, divisa perciò in 8 page frame, numerati
da 0 a 7 ( 3). Nei calcolatori reali, il numero di page frame nella memoria centrale va
da poche decine fino ad alcune migliaia nelle macchine più grosse.
1
Quest’ultimo concetto è fondamentale, in quanto contrapposto a quello che si usa nella cosiddetta segmentazione, in cui
invece il programmatore deve essere a conoscenza dell’esistenza dei segmenti.
2
Non è detto, comunque, che l’aggiornamento nella memoria secondaria debba essere effettuato subito.
3
Nel precedente esempio, invece, la memoria centrale era da 4 K, per cui conteneva un unico page frame.
Autore: Sandro Petrizzelli
12
aggiornamento: 6 luglio 2001
Compendio sulla memoria: memoria cache e memoria virtuale
Spazio di indirizzamento
da 64 K
Memoria centrale
da 32 K
0
4096
8192
12288
16384
20480
24576
28672
32768
36864
40960
45056
49152
53248
57344
61440
0
Pagina 0
4096
Pagina 1
8192
Pagina 2
12288
Pagina 3
16384
Pagina 4
20480
Pagina 5
24576
Pagina 6
28672
Pagina 7
Page Frame 0
Page Frame 1
Page Frame 2
Page Frame 3
Page Frame 4
Page Frame 5
Page Frame 6
Page Frame 7
Pagina 8
Pagina 9
Pagina 10
Pagina 11
Pagina 12
Pagina 13
Pagina 14
Pagina 15
La memoria virtuale del nostro esempio sarebbe implementata, al livello 2 (livello
della macchina standard), per mezzo di una tabella delle pagine, contenente 16
elementi (tanti quante sono le pagine). Il meccanismo della paginazione funziona
allora nel modo seguente:
4
•
quando il programma tenta di accedere alla memoria per prelevare dati o
memorizzarli oppure per prelevare istruzioni o saltare, genera dapprima un
indirizzo a 16 bit corrispondente ad un indirizzo virtuale compreso tra 0 e
65535 ( 4);
•
tale indirizzo virtuale a 16 bit può essere interpretato in vari modi; in
questo nostro esempio, supponiamo che esso venga visto come diviso in
due parti: i primi 4 bit corrispondono al numero di pagine virtuale (da 0
a 15), mentre i restanti 12 bit individuano una locazione all’interno della
pagina selezionata. Ad esempio, nella figura seguente si considera un
indirizzo di valore 12310: separando i primi 4 bit dai restanti 12, si
individua la pagina 3 e, in essa, l’indirizzo 22:
Per generare questo indirizzi si possono usare gli indici, l’indirizzamento indiretto e tutte le altre tecniche comuni.
aggiornamento: 6 luglio 2001
13
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Appendice 4
0
Pagina 0
0-4095
Pagina 1
4096-8191
Pagina 2
8192-12287
Indirizzo virtuale a 16 bit
Pagina 3
12288-16383
4 bit (numero di pagina virtuale)
12 bit (indirizzo entro la pagina virtuale)
Pagina 4
16384-20479
Pagina 5
...
Pagina 6
...
Pagina 7
...
Pagina 8
...
Pagina 9
...
Pagina 10
...
Pagina 11
...
Pagina 12
...
Pagina 13
53248-57343
Pagina 14
57344-61439
Pagina 15
61440-65535
0 1 1 0 0 0 0 0 0 0 1 0 1 1 0
Numero di
pagina: 3
Indirizzo entro la pagina
virtuale: 22
Possibile struttura di un indirizzo virtuale a 16 bit
Da notare che l’indirizzo virtuale 22, all’interno della pagina 3, corrisponde
all’indirizzo fisico 12310 (=12288+22);
5
•
quando il sistema operativo si accorge che il programma richiede la pagina
virtuale 3, deve scoprire dove essa è collocata. Ci sono allora, nel nostro
esempio, 9 distinte possibilità: le prime 8 sono relative alla posizione della
pagina virtuale richiesta in memoria centrale (dato che quest’ultima è
costituita da 8 page frame), mentre l’ultima possibilità è che la pagina
virtuale non si trovi nella memoria principale, bensì nella memoria
secondaria (dato che non necessariamente tutte le pagine virtuali possono
stare contemporaneamente nella memoria centrale). Per capire quale
possibilità risulta verificata, il sistema operativo legge il contenuto della
tabella delle pagine: ogni elemento di tale tabella corrisponde ad una
precisa pagina virtuale e ne indica l’attuale posizione;
•
una possibile struttura della tabella delle pagine, sempre con riferimento
al nostro esempio, è riportata nella prossima figura. Si suppone che ogni
elemento della tabella abbia 3 campi: il primo campo è composto da un
unico bit, il quale indica se la pagina virtuale si trova in memoria
principale (valore 1) oppure no (valore 0); il secondo campo (da 12 bit)
fornisce l’indirizzo di memoria secondaria in cui si trova la pagina virtuale
( 5), nel caso ovviamente in cui essa non si trovi nella memoria centrale;
l’ultimo campo (4 bit), invece, indica il page frame della memoria centrale
in cui si trova la pagina, qualora ovviamente essa non si trovi nella
memoria secondaria. E’ ovvio che l’uso del secondo campo esclude quello
del terzo campo e viceversa, a seconda del valore del primo campo.
In particolare, vengono indicati la pista ed il settore del disco.
Autore: Sandro Petrizzelli
14
aggiornamento: 6 luglio 2001
Compendio sulla memoria: memoria cache e memoria virtuale
Pagina 0
Pagina 1
Pagina 2
Pagina 3
Pagina 4
Generico elemento della tabella delle pagine
Pagina 5
x 0 0
1 1 0 1 1
0
1
1 0
1
1
1
Pagina 6
0
Page
frame
Indirizzo nella
memoria secondaria
Pagina 7
Pagina 8
1 se la pagina è nella memoria centrale
0 se la pagina è nella memoria secondaria
Pagina 9
Pagina 10
Pagina 11
Pagina 12
Pagina 13
Pagina 14
Pagina 15
La tabella delle pagine contiene tanti elementi quante sono le pagine virtuali. Ogni elemento è
costituito, in questo esempio, da 3 campi, rispettivamente da 1 bit (flag), 12 bit (indirizzo di memoria
secondaria) e 4 bit (pagine frame nella memoria principale)
Supponiamo adesso che la pagina virtuale richiesta (la numero 3) si trovi nella
memoria principale, per cui il primo bit dell’elemento corrispondente, nella tabella
delle pagine, è posto ad 1. Gli ultimi 3 bit di questo stesso elemento indicano il
page frame in cui è contenuta la pagina (si tratta del page frame 6 nell’ultima
figura): tali 3 bit vengono allora posti nei 3 bit più a sinistra del MAR (Memory
Address Register), mentre l’indirizzo all’interno della pagina virtuale, ossia i 12 bit
più a destra dell’indirizzo virtuale originale, vengono posti nei 12 bit più a destra
del MAR. In questo modo, viene formato un indirizzo di memoria centrale a 15 bit
(adeguato perciò per una memoria fisica da 32K come quella presa in
considerazione in questo esempio), così come mostrato nella figura seguente:
Indirizzo virtuale a 16 bit
0 0 1 1 0 0 0 0 0 0 0 1 0 1 1 0
42444444
3
14243 144444
Tabella delle pagine
0
1
2
3 1 0 0 1 1 0 1 1 0 1 1 0 1 1 1 0
4
5
6
7
8
9
10
11
12
13
14
15
64748
1
1
644444
47444444
8
0 0 0 0 0 0 0 0 1 0 1 1 0
MAR
Formazione di un indirizzo di memoria partendo da un indirizzo virtuale
aggiornamento: 6 luglio 2001
15
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Appendice 4
A questo punto, l’hardware può usare l’indirizzo contenuto nel MAR per prelevare
la parola desiderata e porla nel registro MBR (Memory Buffer Register) oppure,
viceversa, può prelevare il contenuto del registro MBR e memorizzarlo all’indirizzo
contenuto nel MAR.
Possiamo ora osservare una cosa: se il sistema operativo dovesse convertire ogni
indirizzo virtuale delle istruzioni di livello 3 in un indirizzo effettivo (secondo il
meccanismo descritto), una macchina di livello 3 con memoria virtuale sarebbe
molto più lenta rispetto a quella senza memoria virtuale, il che implicherebbe
l’abbandono del concetto di memoria virtuale, proprio perché non foriero di
benefici. Al contrario, per velocizzare il processo di “traduzione” degli indirizzi
virtuali in indirizzi fisici, generalmente la tabella delle pagine viene memorizzata in
registri speciali dell’hardware e la stessa “traduzione” viene effettuata direttamente
nell’hardware. Una soluzione alternativa potrebbe essere invece quella di
mantenere la tabella delle pagine nei registri veloci e di far fare la traduzione al
microprogramma, per mezzo quindi di una programmazione esplicita: la maggiore o
minore
velocità di questa soluzione rispetto alla precedente dipende
dall’architettura del livello della microprogrammazione, ma comunque essa ha il
vantaggio di non richiedere circuiti speciali e modifiche dell’hardware.
La paginazione su richiesta
Nell’esempio considerato nel paragrafo precedente, abbiamo supposto che la
pagina virtuale ricercata dal programma si trovasse nella memoria centrale; è
chiaro che non necessariamente questo si verifica, in quanto alcune pagine virtuali
potrebbero essere contenute nella memoria secondaria, a causa ad esempio dei
limiti di capienza della memoria centrale. Quando la pagina virtuale richiesta non
si trova nella memoria centrale, si dice che si è verificato un page fault (mancanza
di pagina); in questa situazione, il sistema operativo deve compiere le seguenti
operazioni:
•
leggere la pagina richiesta nella memoria secondaria;
•
trasferirla nella memoria principale;
•
aggiornare la tabella delle pagine con la nuova posizione della pagina
richiesta;
•
ripetere l’esecuzione dell’istruzione che ha causato il page fault.
Avendo una macchina dotata di memoria virtuale, è possibile iniziare un
programma anche se nessuna parte del programma stesso si trova nella memoria
centrale: basterà infatti inizializzare la tabella delle pagine per indicare che tutte le
pagine virtuali si trovano nella memoria secondaria (primo bit a 0). Quando la CPU
prova a prelevare la prima istruzione del programma, ottiene subito un page fault,
il che implica che la pagina contenente tale istruzione venga caricata in memoria
principale e ovviamente che il corrispondente elemento della tabella delle pagine
venga aggiornato. A questo punto, l’esecuzione dell’istruzione può cominciare. Se,
ad esempio, essa richiede due indirizzi in pagine diverse tra loro e diverse
dall’unica pagina caricata in memoria principale, si hanno altri due page fault e
quindi altri due trasferimenti dalla memoria secondaria a quella principale (con
relativi aggiornamenti della tabella delle pagine). L’istruzione successiva potrà
eventualmente causare uno o più altri page fault e così via.
Autore: Sandro Petrizzelli
16
aggiornamento: 6 luglio 2001
Compendio sulla memoria: memoria cache e memoria virtuale
Questo modo di operare su una memoria virtuale prende il nome di paginazione
su richiesta: le pagine vengono infatti caricate nella memoria principale solo se ne
viene fatta esplicita richiesta. Chiaramente, dopo aver eseguito un certo numero di
istruzioni del programma, ci saranno molte pagine virtuali caricati nella memoria
principale e quindi i page fault saranno molto meno frequenti, tanto meno quanto
più capiente è la memoria principale.
Politiche per la sostituzione delle pagine
Fino ad ora, abbiamo sempre supposto implicitamente che, dovendo caricare una
pagina virtuale dalla memoria secondaria a quella centrale, ci sia un page frame
libero disposto ad accoglierla. Generalmente, questo non succede (tutti i frame page
sono occupati) ed è quindi necessario liberarne uno (cioè spostare il suo contenuto
nella memoria secondaria) per ospitare la pagine virtuale richiesta dalla CPU.
Bisogna allora implementare un algoritmo per la scelta del page frame da liberare,
ossia quindi della pagina virtuale da “sostituire”.
Il metodo più semplice è quello della scelta casuale, ma non si tratta
decisamente del metodo migliore: infatti, se capita che venga scelta la pagina
contenente la prossima istruzione da eseguire, si verificherà un altro page fault non
appena la CPU cercherà di acquisire quella istruzione, con conseguente
rallentamento dell’esecuzione. Di conseguenza, molti sistemi operativi tentano di
individuare la pagina virtuale meno utile tra quelle presenti, ossia quella la cui
mancanza avrebbe il minore effetto negativo sul programma in corso.
Un modo abbastanza intuitivo di far questo consiste nell’individuare la pagina
che con meno probabilità sarà usata a breve. Dal punto di vista pratico, questo lo
si può ottenere individuando la pagina usata meno di recente, dato che la
probabilità a priori che non debba essere usata è alta. Si parla allora di metodo
Least Recently Used (LRB). Generalmente, questo metodo funziona bene, ma ci
sono comunque situazioni “patologiche” in cui invece fallisce miseramente.
Un altro algoritmo è quello cosiddetto First In First Out (FIFO): esso toglie la
pagina caricata meno di recente, a prescindere da quando sia stata referenziata. Si
procede allora nel modo seguente:
•
ad ogni page frame della memoria centrale viene associato un contatore,
memorizzato nella tabella delle pagine;
•
inizialmente, tutti i contatori sono posti a 0;
•
dopo ogni operazione di caricamento per page fault, il contatore di tutte le
pagine già presenti in memoria viene incrementato di uno, mentre invece
quello della pagina appena introdotta è posto a 0;
•
in questo modo, quando è necessario scegliere la pagina da togliere, si
prende quella con il contatore più alto, visto che è stata presente per il
maggior numero di pagine e quindi è stata caricata prima di qualsiasi altra
pagina nella memoria, per cui si spera abbia la maggiore probabilità a
priori di non essere più necessaria.
Facciamo osservare che, se la pagina che deve essere “sfrattata” dalla memoria
principale non è stata modificata da quando è stata letta (cosa molto probabile se
contiene parte del programma invece di dati), non è necessario riscriverla nella
memoria secondaria, poiché ne esiste già una copia fedele e quindi è inutile
aggiornamento: 6 luglio 2001
17
Autore: Sandro Petrizzelli
Appunti di “Calcolatori Elettronici” – Appendice 4
“perdere tempo”. Se invece essa è stata modificata, allora la copia nella memoria
secondaria non è fedele e quindi bisogna necessariamente riscriverla.
Il modo più semplice per indicare se una pagina è stata modificata o meno nel
tempo in cui è rimasta nella memoria principale, è quello di associarle un bit nella
tabella delle pagine: il bit viene inizializzato a 0 quando la pagina viene caricata e
viene posto ad 1 in corrispondenza della prima modifica. Esaminando questo bit, il
sistema operativo può dunque capire se ci sono state modifiche (si parla di pagina
sporca) o meno (si parla di pagina pulita) ed agire di conseguenza.
E’ chiaramente auspicabile mantenere un alto rapporto tra pagine pulite e pagine
sporche, in modo da minimizzare il numero di riscritture nella memoria secondaria.
Quasi tutti i calcolatori sono comunque in grado di copiare le pagine dalla memoria
centrale alla secondaria mentre la CPU sta lavorando, usando ad esempio il DMA
(Accesso Diretto alla Memoria) o le apposite unità di canale. Addirittura, alcuni
sistemi operativi approfittano di questo parallelismo tutte le volte che il disco è
libero: viene individuata una pagina sporca, preferibilmente con alta probabilità di
essere scritta a breve (perché vecchia), e ne viene imposta la copia sul disco,
nonostante non ci sia stata ancora la necessità di togliere la suddetta pagina dalla
memoria centrale. Naturalmente, può capitare che quella stessa pagina venga
“risporcata” immediatamente dopo il procedimento di copiatura o addirittura
durante: in questa situazione, se è vero che la “copia anticipata” è stata inutile, è
anche vero che il costo di tale operazione è stato piccolo, dato che il disco era
comunque inutilizzato e la CPU era comunque in grado di lavorare dopo aver
imposto la copia. Le scritture su disco fatte con l’intenzione di “pulire” le pagine
sporche sono chiamate scritture false.
La dimensione delle pagine e la frammentazione
Quando il programma utente ed i suoi dati riempiono totalmente un certo
numero di pagine, non c’è spazio sprecato quando essi si trovano nella memoria
centrale. Viceversa, se essi non riempiono esattamente tutte le pagine, c’è
senz’altro dello spazio inutilizzato nell’ultima pagina. Questo “problema” va sotto il
nome di frammentazione.
Supponiamo che la dimensione di pagina sia di N parole: si può subito dire che
la quantità media di spazio sprecato nell’ultima pagina è di N/2 parole; per ridurre
tale quantità, bisogna evidentemente ridurre N, ossia fare le pagine più piccole. Ma
fare pagine piccole significa fare più pagine e quindi anche ingrandire la tabella
delle pagine: se tale tabella è gestita dall’hardware, il suo ingrandimento comporta
un numero maggiore di registri per la sua memorizzazione e dei relativi circuiti,
ossia quindi un costo maggiore del calcolatore. Non solo, ma sarà anche necessario
più tempo per caricare o salvare tali registri tutte le volte che il programma viene
fermato e poi fatto ripartire.
Ancora, l’uso di pagine piccole rende inefficiente l’uso di memorie secondarie con
lunghi tempi di accesso, come tipicamente i dischi: infatti, dato che bisogna
aspettare 10 ms o più per accedere alla prima parola da trasferire e iniziare il
trasferimento, è preferibile poter trasferire grossi blocchi di informazioni, dato che
il tempo di trasferimento risulta generalmente minore della somma del tempo di
ricerca delle parole e del tempo di rotazione del disco. Al contrario, se la memoria
secondaria non presenta ritardo rotazionale (ad esempio una memoria a nuclei), il
tempo di trasferimento è proporzionale solo alla dimensione del blocco e quindi
sarebbe opportuno avere pagine piccole.
Autore: Sandro Petrizzelli
18
aggiornamento: 6 luglio 2001
Compendio sulla memoria: memoria cache e memoria virtuale
Tutte queste considerazioni mostrano che è sempre necessario trovare un
compromesso, sulla base delle proprie esigenze e dell’hardware a disposizione, per
scegliere una dimensione delle pagine ottimale. Una nota legge dell’informazione
(legge di Amdahl) suggerisce che è sempre opportuno ottimizzare il caso più
frequente: la scelta delle dimensioni delle pagine andrà perciò fatta in modo da
ottimizzare le situazioni che si presentano con maggior frequenza, anche
eventualmente a scapito di quelle meno frequenti.
Autore: Sandro Petrizzelli
e-mail: [email protected]
sito personale: http://users.iol.it/sandry
aggiornamento: 6 luglio 2001
19
Autore: Sandro Petrizzelli