\item kleiner als das zu cachende Medium (Hauptspeicher)
\item schneller als das zu cachende Medium (Hauptspeicher)
\item transparent, \dash es wird nicht auf den Cache, sondern auf das zu cachende Medium logisch zugegriffen (die \acs{CPU} adressiert den Hauptspeicher und nicht den Cache)
\item konsistent, \dash alle Instanzen derselben \acf{HSA} haben denselben Wert
\item kohärent, \dash beim Zugriff auf eine \acs{HSA} wird immer der aktuelle Wert geliefert
\end{itemize}
\bigskip
\begin{Hinweis}[frametitle={Anmerkung: Kohärenz und Konsistenz}]
Kohärenz ist Pflicht und Konsistenz ist Wunsch, da aus Konsistenz Kohärenz folgt. Zeitweise kann aber auf Konsistenz verzichtet werden.
\end{Hinweis}
\subsection{Lokalität des Zugriffmusters}\index{Lokalität}
Verantwortlich dafür, dass der Cache Geschwindigkeitsvorteile bringen kann.
\begin{description}
\item[räumliche Lokalität] Wenn auf eine Adresse zugegriffen wird, wird auch auf naheliegende Adressen zugegriffen.
Hinweis: Insbesondere die räumliche Lokalität ist beim Zugriff auf Programmcode und Nutzdaten sehr unterschiedlich (Programmcode: sequentiell, Nutzdaten zufällig innterhalb von Speicherblöcken).
$\Rightarrow$ Moderne \acsp{CPU} weisen getrennte Caches für Programmcode und Nutzdaten auf!
\end{Hinweis}
\subsection{Begriffe}
\begin{description}
\item[Hit]\index{Hit-Rate} Zugriff auf \acl{HS}-Daten, welcher aus dem Cache bedient werden kann
\item[Miss]\index{Miss-Rate} Zugriff auf \acl{HS}-Daten, welcher \textit{nicht} aus dem Cache bedient werden kann und deshalb der Cache die Daten erst aus dem \acl{HS} holen muss.
\item[Hit-Rate] Anteil der \enquote{erfolgreichen} Zugriffe an allen Zugriffen. \newline
\item Größe (und Größenverhältnis) von Cache und \acl{HS}
\end{itemize}
Hinweis: $\text{Hit-Rate}_\text{erreicht}\geq\frac{\text{Größe (Cache)}}{\text{Größe HS}}$ (Wahrscheinlichkeit, das ein beliebiger Zugriff eine bereits im Cache gespeicherte Adresse betrifft)
$\Rightarrow$ sobald das Zugriffsmuster Lokalität aufweist, ergibt sich eine bessere Hit-Rate
\subsection{Betriebszustände des Cache}
\begin{description}
\item[kalter Cache]\index{Cache!kalter Cache} bei Betriebsbeginn ist der Cache leer
\item[sich erwärmender Cache]\index{Cache!erwärmender Cache} Während des Betriebs wird der Cache mit immer mehr Daten geladen und die Hit-Rate steigt.
\item[heißer Cache]\index{Cache!heißer Cache} Der Cache ist nach einer gewissen Betriebszeit (nahezu) voll. Die Hit-Rate erreicht das systembedingte Maximum.
In \autoref{fig:cpu_cache_look_aside} sind \acs{CPU}, Cache und \acs{HS} über einen Bus miteinander verbunden. Die Anfrage durch die CPU geht auf dem Bus an beide und ggf. antworten beide, \dash die schnellere Antwort gewinnt.
Schreibzugriff durch die \acs{CPU} findet im \acl{HS} statt. Parallel dazu müssen die Daten im Cache invalidiert (schlecht) oder ebenfalls geschrieben werden (gut).
&$\oplus$ gute klassische Kombination, da die physische Gegebenheit vorhanden ist, um direktes Schreiben in Cache und Rückschreiben vom Cache in \acs{HS} zu ermöglichen
\item[\acf{HSS}] gleich große Speicheranteile des Hauptspeichers. Eine \acs{HSS} sollte $2^m$\acfp{HSA} beinhalten, um eine einfache Umrechung von \acs{HSS}-Nummern und \acs{HSA} zu ermöglichen.
\item[$\Rightarrow$] bei jedem Zugriff auf eine \acf{HSA} muss überprüft werden, ob die gekürzte \acs{HSA} einem der Tags von validen \acl{CL} entspricht!
\item Parallel $\Rightarrow$ gleichzeitiges Vergleichen der angelegten (gekürzten) \acs{HSA} mit allen Tags über jeweils einen eigenen Komparator in jeder \acs{CL}. \newline
Nachteil: \acl{HW}-Aufwand für Komparator in jeder \acs{CL}.
Möchte man eine Wertetabelle für einen $n$-Bit-Komparator erstellen, sieht man, dass zu viele Zeilen in der Wertetabelle ($2^{2n}$\ldots) sind $\Rightarrow$ besser mit kaskadierbaren 1-Bit-Komparatoren
\textsf{\textbf{Komparator für Gleichheit}}\newline
\textit{Hinweis}: im Cache reicht der Vergleich auf Gleichheit. \autoref{fig:cache_komparator_3} zeigt 1-Bit-Komparatoren für einen Gleichheits-Komparator.
\node[font=\footnotesize,align=center] at (3.25, -2) (mBesc) {Nummer der\\\acs{CL} im \acs{DMC}};
\draw (4, 0) rectangle ++(1.5,1);
\node at (4.75, 0.5) (kBit) {$k$-Bit};
\draw[->,very thick] (4.75,0) -- ++(0,-0.6);
\node[font=\footnotesize,align=center] at (4.75, -1.1) (kBesch) {Position innerhalb\\einer \acs{HSS}};
\end{tikzpicture}
\caption{Direct-Mapped-Cache-Line}
\label{fig:direct_mapped_cache_line}
\end{wrapfigure}
Beim \acf{DMC} gibt es für jede \acs{HSS} nur/genau eine \acs{CL}, in welche diese \acs{HSS} eingelagert werden kann. \newline
$\Rightarrow$ es ist nur ein Komparator nötig
Es ist eine Hash-Funktion notwendig, welche die gekürzte \acs{HSA} auf die \acs{CL}-Nummer abbildet. Die einfachste Hash-Funktion ist \enquote{Modulo} (Rest einer Ganzzahldivision).
Modulo ist besonders einfach, falls der Divisor eine Zweierpotenz (\zB$2^m$) an Bits ist, denn dann stellen die niederwertigsten $m$ Bit den gesuchten Rest dar! Nachteil ist, dass dadurch nur Zweierpotenzen an \acsp{CL} möglich sind.
Viele Paare von \acs{HSS} können nicht gleichzeitig im Cache gehalten werden (bei gleichem Ergebnis der Hash-Funktion). Eine mögliche Problemlösung: Ausweichfunktion (wird aus Zeitgründen beim Cache nicht verwendet).
Für jede \acs{HSS} gibt es genau $n$\acl{CL}, in welche die \acs{HSS} eingelagert werden kann.
Realisierung über $n$\acs{DMC}, welche alle jeweils gleich aufgebaut sind.
\subsection{Kollision}
Falls beim Einlagern einer \acl{HSS} in den Cache bereits alle für diese \acs{HSS} in Frage kommenden \aclp{CL} belegt sind, handelt es sich um eine Kollision.
Eine Kollision kann frühestens auftreten:
\begin{itemize}[noitemsep]
\item beim \acs{VAC}: bei vollem (heißen) Cache
\item beim \acs{DMC}: beim zweiten Zugriff
\item beim $n$-Wege-\acs{AC}: beim $(n+1)$-ten Zugriff
\end{itemize}
Wie groß ist die Kollisionswahrscheinlichkeit?
\begin{tabular}{c@{}llccl}
\textbullet~ &\acs{DMC}: &$p_\text{Kollision beim 2. Zugriff}$&$=$&$\frac{1}{\text{Anzahl \acs{CL}}}$&$=\frac{1}{2^m}$\\[1.5ex]
\textbullet~ &$n$-Wege-\acs{AC}: &$p_\text{Kollision beim n+1 Zugriff}$&$=$&$(\frac{1}{2^m})^n$&$=(\frac{1}{2^{mn}})$
Wenn eine \acs{HSS} in den Cache eingelagert werden soll, muss eine andere aus dem Cache entfernt werden. Eine Kollision ist Voraussetzung für Verdrängung. Mit einer Verdrängungsstrategie wird darüber entschieden, welche der möglichen \acs{HSS} verdrängt wird. In \autoref{sec:verdraengungsstrategie} wird auf die Verdrängungsstrategie eingegangen.
\begin{Hinweis}
Eine Verdrängungsstrategie ist nur notwendig beim \acs{VAC} und beim $n$-Wege-\acs{AC}. Beim \acs{DMC} braucht man \textit{keine} Verdrängungsstrategie!
\end{Hinweis}
\subsection{Adressrechnen mit Cache}
\columnratio{0.6}
\begin{paracol}{2}
\begin{tabular}{c@{}ll}
\textbullet~ & 2-Wege-\acs{AC}: &$n=2$\\[1ex]
\textbullet~ & 2 \acs{DMC} mit jeweils 8 \acs{CL}: &$m=3$\\[1ex]
\autoref{fig:adressrechnen} zeigt Cache A (links) und Cache B (rechts), mit denen im folgenden gerechnet wird. Die angefragten \aclp{HSA} müssen auf 12-Bit erweitert werden, denn hier ist eine \acl{HSA} 12-Bit groß, wie in \autoref{fig:5_Bit_Tag} zu sehen ist.
\enquote{Blick in die Zukunft} bzw. \enquote{Kristallkugel}$\Rightarrow$ nicht möglich!
\textit{Realisierung für Benchmarking}: \newline
Zweimaliger Durchlauf für genau dieselben Parameter. Der erste Durchlauf für Logfile und zweiter Durchlauf mit optimaler Strategie anhand des Logfiles.
\subsubsection{First-In-First-Out (FIFO)}
Bei der \acf{FIFO}-Strategie wird die \acs{HSS}, welche sich am längsten im Cache befindet, verdrängt (klassische Warteschlangenbedienstrategie).
\textit{Aufwand}:\newline
Timestamp in jeder \acl{CL}. Bei Verdrängung: Suche nach dem Minimum der Timestamps (sehr aufwändig!).
Verwaltung der \acsp{CL} als einfach verwaltete Liste, \dash Zeiger auf den Nachfolger in jeder \acs{CL}. Globalen Zeiger auf den ersten und letzten Eintrag für Verdrängung und Einlagerung.
\textit{Schlecht unterstützte, aber häufige Zugriffsmuster}: \newline
Ständig genutzte Datenstücke werden genauso schnell verdrängt wie Daten, die nur ein einziges Mal gebraucht werden. Anders gesagt: Daten, die lange nicht benötigt wurden, werden auch nicht schneller verdrängt, als Daten, die genauso lange im Cache sind, aber erst kürzlich gebraucht wurden.
Timestamp mit Update bei jedem Zugriff $\Rightarrow$ Die Suche bei Verdrängung ist zu aufwändig
\textit{Besser}:\newline
Verwaltung als \textit{doppelt} verkettete Liste, \dash Zeiger auf Vorgänger \textit{und} Nachfolger in jeder \acs{CL}. Globalen Zeiger auf ersten und letzten Eintrag.
Häufigkeit der Zugriffe wird nicht berücksichtigt, \dash vielfach genutzte \aclp{HSS} werden genauso verdrängt wie \acs{HSS} mit nur einem Zugriff)
\subsubsection{Least-Frequently-Used (LFU)}
Bei der \acf{LFU} Strategie wird die \acl{HSS} verdrängt, welche bisher am seltensten (\enquote{am wenigsten häufig}) verwendet wurde.
\columnratio{0.35}
\begin{paracol}{2}
\textit{Aufwand (für Statusinfo):}
\begin{itemize}[noitemsep]
\item Benutzungszähler
\item Einlagerungszeit
\end{itemize}
\switchcolumn
\medskip
Häufigkeit=$\frac{\text{Zugriffe}}{\text{Zeit}}$
$\Rightarrow$ Bei Verdrängung aufwändige Berechnung und Suche
\end{paracol}
\newpage% Nur für's Layout
\textit{Problem (Zugriffsmuster)}: \newline
Neu eingelagerte Seiten werden schnell wieder verdrängt, wenn sich der Zugriffszähler am Anfang nicht schnell genug erhöht und andere etablierte Seiten eine höhere Häufigkeit aufgrund vieler \enquote{alter} Zugriffe aufweisen.
\textit{Lösungsansatz}: \newline
Zugriffe müssen \enquote{altern}, \dash alte Zugriffe werden weniger stark gewichtet als neue Zugriffe.
\textit{Mögliche Implementierung}: \newline
Mehrere Zugriffszähler in jeder \acs{CL} für die letzten $i$ Zeitscheiben.
Dividieren ist damit überflüssig (alle Zeitscheiben sind gleich lang): Addition der mit $2^j$ gewichteten Zähler als \enquote{Häufigkeit} ($j=i-1$ für jüngste Zeitscheiben, $j=0$ für älteste Zeitscheibe).
\textit{Weiterhin}: \newline
Bei der Verdrängung gibt ein Problem bei der Suche nach der niedrigsten \enquote{Häufigkeit}. \newline
$\Rightarrow$ evtl. Lösung über eine bei Beginn jedes Zeitslots neu aufzubauende verkettete Liste. \newline
\phantom{$\Rightarrow$}Hier gibt es viel Platz für Optimierungen der \acs{CPU}-Hersteller.
\subsection{Cache bei Mehrprozessor-/Mehrkernsystemen}\index{Mehrkernprozessor}
In \autoref{fig:cpu_mehrprozessor} besitzt jeder Kern einen eigenen Cache. Möglich wäre auch ein gemeinsamer Cache. Verglichen werden diese beiden Möglichkeiten in \autoref{tbl:mehrprozessor_cache}.
\item gemeinsamer Cache (ungünstige Performance aufgrund eines Bus)
\item Snoop-Logik, \dash jeder Cache schnüffelt bei den anderen Caches bzw. beim \acs{HS}, welche Daten geschrieben wurden und invalidiert diese ggf. im eigenen Cache.
\end{itemize}
Tatsächlich haben moderne \acsp{CPU} beim L1-Cache getrennte Caches für jeden Kern und beim L3-Cache einen gemeinsamen Cache. Ob eine Snoop-Logik verwendet wird, ist davon abhängig, ob es für das Level notwendig ist oder nicht.
Ein \acl{HS}-Wort wird über eine \acl{HSA} (binäre, ganze, nicht negative Zahl) angesprochen.
Jedes \acl{HS}-Wort hat eine feste Wortbreite (im besten Fall gleich der \acs{CPU}-Wortbreite, also 64-Bit).
Im folgenden gehen wir von 1-Bit-Worten aus:
\begin{center}
$0\leq\text{\acs{HSA}}\leq(\text{\acs{HS}-Größe in Worten})-1$
\end{center}
Ein 1-Bit-Kondensatorspeicher wird in \autoref{fig:hs_kondensator} gezeigt. Für jedes \acl{HS}-Wort gibt es eine Select-Leitung. Für jedes Bit des Wortes gibt es einen Kondensator und einen Transistor. Diese werden dann parallel geschaltet.
Mehrere 1-Bit-Worte hängen an der selben Datenleitung, werden aber über unterschiedliche Select-Leitungen angesteuert.
Eine matrixförmige Speicherorganisation: zweidimensionale Anordnung der Speicherwerte in Zeilen und Spalten, siehe \autoref{fig:matrix_decoder} auf \autopageref{fig:matrix_decoder}.
$a_3a_2$ gibt die Zeilennummer an und $a_1a_0$ die Spaltennummer. Somit reicht es statt eines 4:16-Decoder einen 2:4 Zeilen- und einen 2:4 Spalten-Decoder zu verwenden.
\textbf{Aufwand}\newline
Anstatt eines 4:16-Decoder mit 64 Transistor, zwei 2:4-Decoder mit $2\cdot8$ Transistoren = 16 Transistoren.
64-Bit \acs{HSA}: als $64:2^{64}$ Decoder: $2^{70}$ Transistoren \newline
als Matrix mit $2^{32}$ Zeilen und $2^{32}$ Spalten: \newline
$32:2^{32}$ Decoder mit jeweils $32\cdot2^{32}$ Transistoren=$2^{37}$ Transistoren \newline
In linearer Organisation betrüge der Decoder-Aufwand das 4-Mrd.-fache! Damit ergibt sich eine große Einsparung beim Decoder-Aufwand!
\textit{Aber}: für die Realisierung des 2. Select-Eingangs wird ein weiterer Transistor je Bit benötigt \newline
$\Rightarrow$ dieselbe Größenordnung Zusatzaufwand wie Einsparung?
Statt eines Spalten-Decoders werden die Datenleitungen aller Speicherbits einer Spalte zusammengeschaltet (als jeweils eine Spalten-Datenleitung). Von diesen wird aber über einen Spalten-Multiplexer ein Spalten-Daten-Signal ausgewählt. Dies wird in \autoref{fig:matrix_multiplexer} dargestellt.
Insgesamt braucht man für einen kleinen Decoder und einem kleinen Multiplexer immer noch deutlich weniger Hardwareaufwand wie für einen großen Decoder.
Vor den Multiplexer kann ein Cache geschaltet werden, sodass eine ganze \acs{HSS} (=Matrixzeile) eingelesen werden kann.
\subsection{Gründe für Matrixorganisation}
\begin{enumerate}
\item weniger Aufwand für Adresscodecodierung
\item Einlesen einer ganzen \acs{HSS} (=Matrixzeile) in den Cache
\item zeilenweiser Refresh des \acs{HS} (wortweiser Refresh-Zyklus dauert viel zu lange)