Befinden sich die benötigten Daten in 70 % der Fälle im L1-Cache, 20 % im L2-Cache, 5 % im L3-Cache und bei weiteren 5 % im Hauptspeicher, so würde sich – ausgehend von den Zahlen in der Tabelle – für die durchschnittliche Speicher-Performance der folgende Wert ergeben:
(0,7 × 4 ns) + (0,2 × 5 ns) + (0,05 × 30 ns) + (0,05 × 220 ns) = 16,30 ns
Die Performance des Cache ist höher, wenn das Programm sequenziell auf den Speicher zugreift. Wird dagegen in zufälliger Reihenfolge auf große Datenstrukturen zugegriffen, dann führt dies in der Regel zu einer höheren Anzahl von Cache-Misses.
Bild 1 enthält ein Beispiel für einen sequenziellen lokalen Zugriff und zeigt das Zugriffsmuster eines 6-Tap-FIR-Filters. Die Ziffern stehen für die Reihenfolge der Zugriffe auf die Werte im Speicher. Beim ersten Zugriff lädt der Cache-Controller die Daten von der ersten Zugriffsadresse sowie die Daten einer bestimmten Anzahl von Folgeadressen in den Cache. Der betreffende Adressenbereich wird als „Cache- Linie“ bezeichnet. Das Abrufen der Daten aus dem langsameren Speicher verursacht mit hoher Wahrscheinlichkeit Wartezeitzyklen in der CPU. Die nächsten fünf Werte werden aber ebenfalls benötigt. Diese Zugriffe erfolgen jetzt jedoch auf den schnellen Cache und nicht auf den langsamen externen Speicher. Damit werden Wartezyklen vermieden. Zugriffe auf die benachbarten Speicherplätze werden als räumlich lokal bezeichnet.
Bei der Berechnung der nächsten Ausgabe werden fünf Werte wiederverwendet und nur einer ist neu (Bild 1). Alle Werte sind aber bereits im Cache vorhanden. Die CPU arbeitet somit verzögerungsfrei. Den erneuten Zugriff auf dieselben Daten bezeichnet man als zeitliche Lokalität – auch sie ist in diesem Beispiel gegeben.
Der Cache nutzt das Phänomen aus, dass Datenzugriffe meist räumlich und zeitlich lokal stattfinden. Zugriffe auf einen langsameren Speicher sind sehr selten; die Mehrheit der Zugriffe kann bei CPU-Geschwindigkeit aus dem Cache-Speicher abgearbeitet werden.
Cache-Architektur am Beispiel des C64-DSPs
Die Speicherarchitektur des TI-DSP C64x (Bild 2) besteht aus einem internen Zwei-Ebenen-Cache. Der Cache der Ebene 1 (L1-Cache) ist in Programm-Cache (L1P-Cache) und Daten-Cache (L1D-Cache) mit einer Größe von je 16 Kbyte aufgeteilt. Auf den Cache der Ebene 1 kann die CPU ohne Wartezeiten zugreifen. Der Cache der Ebene 1 kann nicht abgeschaltet werden. Der L2-Cache ist konfigurierbar und kann in L2-SRAM (adressierbarer Speicher auf dem Chip) und in L2-Cache aufgeteilt werden. Beim DSP TMS320C6416 beträgt die Größe des L2 z.B. 1 Mbyte. Jedoch kann nur alle zwei Zyklen ein Zugriff abgearbeitet werden. Der externe Speicher kann bis zu 2 Gbyte groß sein. Die Geschwindigkeit hängt von der verwendeten Speichertechnologie ab, beträgt jedoch in der Regel ca. 100 MHz.
In Bild 3 ist der L1-Programmcache des DSP C64x beispielsweise 16 Kbyte groß und besteht aus 512 32-byte-Zeilen. Jede Zeile bildet stets dieselben festen Adressen im Speicher ab. Beispielsweise werden die Adressen 0x0 bis 0x19 grundsätzlich in Zeile 0 zwischengespeichert, die Adressen 3FE0h bis 3FFFh in Zeile 511. Da die Kapazität des Cache dann erschöpft ist, werden die Adressen 4000h bis 4019h wieder auf Zeile 0 abgebildet und so weiter. Zu beachten ist, dass eine Zeile exakt ein Befehlspaket enthält.
Der Gültigkeitszustand einer Zeile wird durch ein Gültigkeitsbit angezeigt. Ein Gültigkeitsbit mit dem Wert „0“ bedeutet, dass die entsprechende Zeile keine gültigen Daten enthält. Bei einer CPU-Anforderung zum Lesen der Adresse 20h teilt der Cache-Controller die Adresse in drei Teile auf: Offset, Set und Tag (Bild 3). Bei direkten Cache-Speichern entspricht „Set“ einer Zeile. Bei der Adresse 20h ist er Eins. Der Controller prüft dann den Tag und das Gültigkeitsbit. Wenn der Cache vorher vollständig gelöscht wurde, d.h., keine Zeile gültige Daten enthält, dann hat das Gültigkeitsbit den Wert „0“ und der Controller registriert einen Cache-Miss.
Ein Cache-Miss hat zur Folge, dass die Zeile, die die angeforderte Adresse enthält, nicht nur in die CPU-Register, sondern auch in den Cache geschrieben wird. Bei einem erneuten Zugriff auf die Adresse 20h teilt der Controller die Adresse erneut in drei Teile auf. Der gespeicherte Tag wird nun mit dem Tag der angeforderten Adresse verglichen. Dieser Vergleich ist notwendig, da mehrere Zeilen im Speicher auf dieselbe Zeile abgebildet werden können. Bei jedem Miss wird als Gegenmaßnahme eine Datenzeile aus dem Speicher in den Cache geschrieben. Um das bestmögliche Ergebnis zu erzielen, muss die Zeile so oft wie möglich wiederverwendet werden, bevor sie durch eine andere Zeile ersetzt wird. Die Wiederverwendung der Zeile bei Zugriff auf unterschiedliche Offsets in dieser Zeile verbessert die räumlichen Offsets von Zugriffen, während die Wiederverwendung derselben Lokalitäten einer Zeile die zeitliche Lokalität von Zugriffen verbessert. Dies ist die grundlegende Strategie zur Optimierung von Speicherzugriffen für eine verbesserte Cache-Performance. Entscheidend ist hier, dass die Zeile wiederverwendet wird, bevor sie durch eine andere ersetzt wird.
Eine Erweiterung des direkt abbildenden Cache-Speichers ist der „Set assoziative Cache“-Speicher (Set-Associative Cache, Bild 4). Der Level-1-Datencache des C6x ist solch ein satzassoziativer Zwei-Wege-Cache mit 16 Kbyte Speicherkapazität und 64-byte-Zeilen. Der Unterschied zu einem direkt abbildenden Cache liegt darin, dass ein Set aus zwei Zeilen besteht, eine Zeile im „Way 0“ und eine Zeile im „Way 1“. Zu diesem Zweck ist der Cache-Speicher in zwei „Ways“ unterteilt, wobei jeder „Way“ aus 8 Kbyte besteht.
Hits und Misses werden in derselben Weise wie bei einem Cache mit direkter Datenabbildung bestimmt, wobei nun zwei Tag-Vergleiche notwendig sind (einer für jeden „Way“), um zu bestimmen, in welchem „Way“ die angeforderten Daten abgelegt sind.
Bei einem Treffer im Weg 0 werden die Daten der Zeile im Way 0 abgerufen, bei einem Treffer im Weg 1 die der Zeile im Way 1. Das LRU-Bit (least recently used) bestimmt, wie die Daten geschrieben werden; es existiert für jedes Set. Das LRU-Bit kann man sich als einen Schalter vorstellen. Der Zustand des LRU-Bit ändert sich, sobald ein Zugriff (Lesen oder Schreiben) auf eine Cache-Zeile stattfindet.