Piccolo appuntino... la cache funziona a blocchi e non a byte.
Per semplicità d'ora in poi noi parleremo di "cella" riferendoci ad un "blocco" (in inglese si parla di "line size"), ovvero un certo numero di locazioni di memoria consecutive (ad esempio 64 byte); e quando parleremo dell'indirizzo della cella ci riferiremo all'indirizzo della prima locazione del blocco.
Per nota diciamo anche che quando, alla ricerca di una cella, la si trova in cache si parla di "cache hit" mentre quando non la si trova siamo in presenza di un "cache miss".
A differenza della memoria le "righe" della cache contengono, oltre al dato, sia l'indirizzo "reale" a cui corrisponde la cella "cacheata" (o parte di esso) sia delle flag che, a seconda del tipo di cache, indicano se la cella è attualmente usata, se è stata modificata rispetto alla "versione" sulla RAM, quand'è stata usata l'ultima volta e via dicendo.
In genere l'ultimo accesso fatto alla cella serve per capire quali celle "scaricare" quando occorre uno "spazio" libero mediante l'algoritmo di eliminazione dell'"usato meno di recente" (o LRU).
Ora... le cache si dividono anche a seconda del "metodo" con cui immagazzinano i dati.
Questi due modi dipendono essenzialmente dal sistema con cui viene fatta la ricerca per vedere se una particolare locazione di memoria è in cache o no.
Stiamo parlando delle cache "associative" e di quelle "direct mapped".
In una memoria direct mapped una locazione di memoria è "cacheabile" (aka la puoi mettere) solo in una locazione della cache.
In pratica basta fare l'indirizzo di memoria modulo la dimensione della cache e si ha un indirizzo "di cache".
Se in quella cella di cache c'è l'indirizzo che interessa a noi bene, altrimenti è un cache miss.
Il vantaggio della cache direct mapped è che è una cosa semplicissima risalire alla posizione della cella partendo dall'indirizzo, basta fare un modulo (che, per le potenze di due, significa semplicemente troncar via i bit più alti), lo svantaggio è che questo sistema è poco flessibile, e può provocare, in casi particolari, situazioni veramente brutte a vedersi (col caso limite di quando si devono spostare dati fra due celle aventi lo stesso "modulo").
L'altro estremo sono le memorie "associative" in cui, semplicemente, l'indirizzo richiesto viene confrontato con l'indirizzo contenuto nelle celle e se c'è la corrispondenza abbiamo un cache hit, altrimenti la locazione che cerchiamo è solo nella RAM.
Niente paura, il "confronto" può essere fatto in parallelo su TUTTE le celle contemporaneamente e con le memorie associative una cella può stare in qualsiasi locazione della cache senza problemi.
Dove le memorie associative invece perdono è nella complessità... perché inserire un comparatore per ogni indirizzo di cella significa usare tante porte logiche il che vuol dire tanti transisor e quindi, ovviamente, tanto spazio sul chip.
Ma visto che una soluzione è troppo drastica e l'altra è troppo costosa spesso si ricorre ad una via di mezzo le "x-way associative" che sono un mix fra le due tipologie.
In pratica nelle memorie associative a X vie una determinata cella può essere solo su X (con X una potenza di 2, tipicamente 4,8 o 16) celle della cache.
Ecco un immagine che mostra la differenza fra una cache direct access ed una 2-way associative:

E qui c'è un grafico che mostra le percentuali di hit e miss tipiche a seconda della tipologia delle cache:

Come si può vedere mentre la cache "direct access" tende a provocare un bel po'di miss già una 2-way associative è decisamente più "performante".
Ed ecco alcuni dati "reali" relativi all'Intel i7 950:
Cita:
L1 Data cache 4 x 32 KBytes, 8-way set associative, 64-byte line size
L1 Instruction cache 4 x 32 KBytes, 4-way set associative, 64-byte line size
L2 cache 4 x 256 KBytes, 8-way set associative, 64-byte line size
L3 cache 8 MBytes, 16-way set associative, 64-byte line size
...e quelli dei PhenomII X4:
Cita:
L1 Data cache 4 x 64 KBytes, 2-way set associative, 64-byte line size
L1 Instruction cache 4 x 64 KBytes, 2-way set associative, 64-byte line size
L2 cache 4 x 512 KBytes, 16-way set associative, 64-byte line size
L3 cache 4 MBytes, 64-way set associative, 64-byte line size
Qui il "line size" è la dimensione della cella (intesa come blocco d'indirizzi consecutivi) in byte.