mirror of
https://github.com/Steffo99/appunti-magistrali.git
synced 2024-11-24 11:14:18 +00:00
134 lines
5 KiB
Markdown
134 lines
5 KiB
Markdown
|
# Memorie
|
||
|
|
||
|
La _memoria_ di un calcolatore solitamente è composta da molteplici strati, realizzati per velocizzare gli accessi.
|
||
|
|
||
|
Ogni strato offre lettura più veloce, ma è più costoso da realizzare e ha consumi più alti.
|
||
|
|
||
|
In ordine di velocità, sono:
|
||
|
|
||
|
- Registers
|
||
|
- Cache L1
|
||
|
- Cache L2
|
||
|
- Cache L`X`
|
||
|
- Random Access Memory
|
||
|
- Solid State Drive
|
||
|
- Hard Disk Drive
|
||
|
|
||
|
## Cache
|
||
|
|
||
|
### Cache L1
|
||
|
|
||
|
Piccola, ma miss penalty bassa
|
||
|
|
||
|
### Cache L2
|
||
|
|
||
|
Molto grande, ma miss penalty alta
|
||
|
|
||
|
### Direct mapping
|
||
|
|
||
|
Una cache _direct mapped_ associa a ogni indirizzo di RAM un **indice** uguale agli **N bit meno significativi** dell'indirizzo dell'inferiore, e un **tag** uguale agli **N bit più significativi**.
|
||
|
|
||
|
Se durante un accesso _il tag di un blocco è diverso dal tag dell'indirizzo a cui vogliamo accedere_, si ha un cache miss.
|
||
|
|
||
|
> Il dato di cui abbiamo bisogno è contenuto nell'indirizzo `0x0ABC` della RAM.
|
||
|
> Il suo indice sarà `0xBC`, e il suo tag sarà `0x0A`.
|
||
|
> Essendo la cache vuota all'inizio, viene immediatamente caricato.
|
||
|
>
|
||
|
> Voglio poi accedere all'indirizzo `0x0BBC` della RAM.
|
||
|
> Controlliamo il blocco con indice `0xBC`: il suo tag è `0x0A`, ma noi stiamo cercando `0x0B`! Si ha quindi un cache miss, e devo andare a prendere dalla memoria il dato che sto cercando e scriverlo sulla RAM.
|
||
|
|
||
|
Inoltre, in ogni blocco di memoria della cache è presente **un bit di validità**, che rappresenta se il dato in cache è stato inizializzato o no: parte da `0` e viene impostato a `1` quando viene caricato dalla RAM un dato nel relativo blocco.
|
||
|
|
||
|
> Un esempio potrebbe essere un processore con blocchi di memoria da 32 bit e indirizzi a 64 bit: la cache, contenente 1024 blocchi di memoria, avrà index a 12 bit, e di conseguenza il tag sarà grande 52 bit.
|
||
|
|
||
|
#### Dimensione blocchi
|
||
|
|
||
|
Fare blocchi grandi o fare blocchi piccoli ha significative differenze sulla velocità della cache:
|
||
|
|
||
|
Avere blocchi più grandi significa che ci saranno meno blocchi in tutta la cache, quindi:
|
||
|
|
||
|
```diff
|
||
|
+ Riduce il miss rate per il principio di località spaziale
|
||
|
- Avendo una quantità minore di blocchi, aumenta la possibilità di conflitto
|
||
|
- La miss penalty è più alta
|
||
|
# La miss penalty è compensabile con tecniche come early restart o critical-word-first
|
||
|
```
|
||
|
|
||
|
Con blocchi più piccoli, invece
|
||
|
```diff
|
||
|
+ Miss penalty minore
|
||
|
+ Più blocchi significa meno conflitti
|
||
|
- E' possibile che abbia bisogno di fare più di una richiesta alla cache, rallentandola significativamente
|
||
|
```
|
||
|
|
||
|
#### Scrittura tramite cache
|
||
|
|
||
|
Quando scrivo in memoria un dato presente nella cache, l'informazione presente nella cache diventa errata.
|
||
|
|
||
|
Si può risolvere questo problema con una politica di riscrittura:
|
||
|
- Write-through
|
||
|
- Write-back
|
||
|
|
||
|
##### Write-through
|
||
|
|
||
|
_Quando **viene scritto** su dato in cache_, aggiorna tutte le memorie che lo contengono.
|
||
|
|
||
|
```diff
|
||
|
+ Non necessita di ulteriore memoria di cache
|
||
|
+ Caricare dati è veloce
|
||
|
+ Se si verifica un write miss non ha per forza bisogno di portare in cache il dato da scrivere
|
||
|
- I write richiedono molto più tempo, soprattutto se ripetuti
|
||
|
```
|
||
|
|
||
|
|
||
|
##### Write-back
|
||
|
|
||
|
Scrivi la modifica di dato solo nella cache, e _marca il blocco come **dirty**_.
|
||
|
|
||
|
Quando un blocco dirty viene **sovrascritto**, aggiorna le memorie che lo contengono.
|
||
|
|
||
|
```diff
|
||
|
+ I write richiedono poco tempo, anche se ripetuti
|
||
|
- Caricare dati dalla RAM è più lento
|
||
|
- Necessita di memoria da dedicare al dirty bit
|
||
|
- Devo fetchare obbligatoriamente i dati da sovrascrivere
|
||
|
```
|
||
|
|
||
|
##### Miglioramenti alle policy
|
||
|
|
||
|
E' possibile utilizzare un **write buffer** invece che fare attendere il tempo di scrittura alla CPU. I dati saranno scritti successivamente, ma prima che questi vengano utilizzati.
|
||
|
|
||
|
```diff
|
||
|
+ Il processore non ha bisogno di fermarsi per le write
|
||
|
- Il buffer potrebbe riempirsi, neganode i vantaggi
|
||
|
- Il buffer utilizza memoria che forse sarebbe stata più utile come cache
|
||
|
```
|
||
|
|
||
|
### Fully associative
|
||
|
|
||
|
Ogni dato può essere messo in qualunque indirizzo della cache.
|
||
|
|
||
|
Richiede che l'indirizzo di origine venga salvato assieme al dato, e tanti comparatori.
|
||
|
|
||
|
### Set associative
|
||
|
|
||
|
Divido tutti i blocchi di cache in vie.
|
||
|
|
||
|
Ogni via può contenere `n` dati.
|
||
|
|
||
|
Un set è l'insieme dei dati che hanno lo stesso index ma sono in vie diverse.
|
||
|
|
||
|
Ogni dato può essere messo in qualunque indirizzo del set a cui appartiene.
|
||
|
|
||
|
Identifico il numero di set di appartenenza facendo `indirizzo % numerodiset`.
|
||
|
|
||
|
Una cache `1`-way Set Associative è una cache Direct Mapped, mentre una cache `numeroentries`-way Set Associative è una cache Fully Associative.
|
||
|
|
||
|
Quando non c'è spazio in un set, rimpiazzo un dato secondo la politica **Least Recently Used**, rimuovendo il dato usato meno recentemente. Posso usare anche la politica **Random**, se ho un'alta associatività.
|
||
|
|
||
|
#### Performance
|
||
|
|
||
|
Più una cache è associativa, più il miss rate sarà ridotto, ma l'associatività richiede un maggior numero di comparatori e potenzialmente più ritardi nella restituzione del dato.
|
||
|
|
||
|
Inoltre, la percentuale di miss non diminuisce linearmente con il numero di vie: dopo un certo numero di vie, i guadagni sono molto ridotti.
|