Innanzitutto l'accesso alla memoria principale è molto costoso. Attualmente una CPU da 2 GHz (la più lenta una volta) ha tick 2G (cicli) al secondo. Una CPU (core virtuale al giorno d'oggi) può recuperare un valore dai suoi registri una volta per tick. Poiché un core virtuale è composto da più unità di elaborazione (ALU - unità logica aritmetica, FPU ecc.), Se possibile, può effettivamente elaborare determinate istruzioni in parallelo.
Un accesso alla memoria principale costa da circa 70ns a 100ns (DDR4 è leggermente più veloce). Questa volta cerca sostanzialmente la cache L1, L2 e L3 e poi tocca la memoria (invia il comando al controller di memoria, che la invia ai banchi di memoria), aspetta la risposta ed è fatta.
100ns significa circa 200 tick. Quindi, in sostanza, se a un programma mancassero sempre le cache a cui accede ogni memoria, la CPU impiegherebbe circa il 99,5% del suo tempo (se legge solo la memoria) in attesa della memoria.
Per velocizzare le cose ci sono le cache L1, L2, L3. Usano la memoria posizionata direttamente sul chip e usando un diverso tipo di circuiti a transistor per memorizzare i bit dati. Ciò richiede più spazio, più energia ed è più costoso della memoria principale poiché una CPU viene generalmente prodotta utilizzando una tecnologia più avanzata e un errore di produzione nella memoria L1, L2, L3 ha la possibilità di rendere la CPU senza valore (difetto), quindi cache di grandi dimensioni L1, L2, L3 aumentano il tasso di errore che diminuisce il rendimento che diminuisce direttamente il ROI. Quindi c'è un enorme compromesso quando si tratta della dimensione della cache disponibile.
(attualmente si creano più cache L1, L2, L3 per essere in grado di disattivare determinate porzioni per ridurre la possibilità che un vero difetto di produzione sia le aree di memoria cache che rendono il difetto della CPU nel suo insieme).
Per dare un'idea di tempismo (fonte: costi per accedere a cache e memoria )
- Cache L1: da 1ns a 2ns (2-4 cicli)
- Cache L2: da 3ns a 5ns (6-10 cicli)
- Cache L3: da 12ns a 20ns (24-40 cicli)
- RAM: 60ns (120 cicli)
Dato che mescoliamo diversi tipi di CPU, queste sono solo stime, ma danno una buona idea di cosa sta realmente accadendo quando viene recuperato un valore di memoria e potremmo avere un hit o un miss in un determinato livello di cache.
Quindi una cache velocizza notevolmente l'accesso alla memoria (60 ns contro 1 ns).
Recuperare un valore, memorizzarlo nella cache per la possibilità di rileggerlo è utile per le variabili a cui si accede spesso, ma per le operazioni di copia della memoria sarebbe ancora lento poiché si legge un valore, si scrive da qualche parte e non si legge mai il valore di nuovo ... nessun hit nella cache, dead slow (oltre a questo può accadere in parallelo poiché abbiamo un'esecuzione fuori servizio).
Questa copia di memoria è così importante che esistono diversi modi per accelerarla. All'inizio la memoria era spesso in grado di copiare la memoria al di fuori della CPU. È stato gestito direttamente dal controller di memoria, quindi un'operazione di copia della memoria non ha inquinato le cache.
Ma oltre a una semplice copia di memoria, era abbastanza comune l'accesso seriale alla memoria. Un esempio è l'analisi di una serie di informazioni. Avere un array di numeri interi e calcolare la somma, la media, la media o ancora più semplice trovare un certo valore (filtro / ricerca) erano un'altra classe molto importante di algoritmi eseguiti ogni volta su qualsiasi CPU per scopi generici.
Quindi, analizzando il modello di accesso alla memoria era evidente che i dati venivano letti in sequenza molto spesso. C'era un'alta probabilità che se un programma legge il valore all'indice i, anche il programma leggerà il valore i + 1. Questa probabilità è leggermente superiore alla probabilità che lo stesso programma legga anche il valore i + 2 e così via.
Quindi, dato un indirizzo di memoria, era (ed è ancora) una buona idea leggere in anticipo e recuperare valori aggiuntivi. Questo è il motivo per cui esiste una modalità boost.
L'accesso alla memoria in modalità boost significa che viene inviato un indirizzo e vengono inviati in sequenza più valori. Ogni invio di valore aggiuntivo richiede solo circa 10 ns (o anche sotto).
Un altro problema era un indirizzo. L'invio di un indirizzo richiede tempo. Per indirizzare gran parte della memoria, è necessario inviare indirizzi di grandi dimensioni. All'inizio significava che il bus dell'indirizzo non era abbastanza grande per inviare l'indirizzo in un singolo ciclo (tick) e che era necessario più di un ciclo per inviare l'indirizzo aggiungendo più ritardo.
Una riga della cache di 64 byte, ad esempio, significa che la memoria è divisa in blocchi distinti (non sovrapposti) di memoria di 64 byte di dimensione. 64 byte indicano che l'indirizzo iniziale di ciascun blocco ha i sei bit di indirizzo più bassi da essere sempre zeri. Pertanto, non è necessario inviare questi sei bit zero ogni volta per aumentare lo spazio degli indirizzi 64 volte per qualsiasi numero di larghezza del bus dell'indirizzo (effetto di benvenuto).
Un altro problema che la riga della cache risolve (oltre a leggere in anticipo e salvare / liberare sei bit sul bus degli indirizzi) è il modo in cui è organizzata la cache. Ad esempio, se una cache sarebbe divisa in blocchi (celle) da 8 byte (64 bit), è necessario memorizzare l'indirizzo della cella di memoria per cui questa cella cache contiene il valore. Se l'indirizzo sarebbe anche a 64 bit, ciò significa che la metà della dimensione della cache viene consumata dall'indirizzo con un sovraccarico del 100%.
Poiché una riga della cache è di 64 byte e una CPU potrebbe utilizzare 64 bit - 6 bit = 58 bit (non è necessario memorizzare i bit zero troppo a destra) significa che possiamo memorizzare nella cache 64 byte o 512 bit con un sovraccarico di 58 bit (sovraccarico dell'11%). In realtà gli indirizzi memorizzati sono anche più piccoli di questo, ma ci sono informazioni sullo stato (come la linea della cache è valida e accurata, sporca e deve essere riscritta in ram ecc.).
Un altro aspetto è che abbiamo una cache set associativa. Non tutte le celle della cache sono in grado di memorizzare un determinato indirizzo, ma solo un sottoinsieme di quelli. Ciò rende ancora più piccoli i bit di indirizzo memorizzati necessari, consente l'accesso parallelo alla cache (è possibile accedere a ciascun sottoinsieme una volta ma indipendentemente dagli altri sottoinsiemi).
Soprattutto quando si tratta di sincronizzare l'accesso alla cache / memoria tra i diversi core virtuali, le loro unità di elaborazione multiple indipendenti per core e infine i processori multipli su una scheda madre (che contiene schede che ospitano fino a 48 processori e oltre).
Questa è fondamentalmente l'idea attuale del perché abbiamo linee di cache. Il vantaggio di leggere in anticipo è molto elevato e il caso peggiore di leggere un singolo byte da una riga della cache e non rileggere mai più il resto è molto scarso poiché la probabilità è molto ridotta.
La dimensione della cache-line (64) è un saggio compromesso scelto tra le cache-line più grandi rende improbabile che l'ultimo byte venga letto anche nel prossimo futuro, la durata necessaria per recuperare l'intera riga della cache dalla memoria (e per riscriverlo) e anche il sovraccarico nell'organizzazione della cache e la parallelizzazione dell'accesso alla cache e alla memoria.