Sie bekommen definitiv eine sogenannte Cache- Resonanz . Dies ähnelt dem Aliasing , ist jedoch nicht genau dasselbe. Lassen Sie mich erklären.
Caches sind Hardwaredatenstrukturen, die einen Teil der Adresse extrahieren und als Index in einer Tabelle verwenden, ähnlich wie ein Array in der Software. (Tatsächlich nennen wir sie Arrays in der Hardware.) Das Cache-Array enthält Cache-Zeilen mit Daten und Tags - manchmal einen solchen Eintrag pro Index im Array (direkt zugeordnet), manchmal mehrere solcher (N-Wege-Satzassoziativität). Ein zweiter Teil der Adresse wird extrahiert und mit dem im Array gespeicherten Tag verglichen. Index und Tag identifizieren zusammen eine Cache-Zeilen-Speicheradresse eindeutig. Schließlich identifiziert der Rest der Adressbits zusammen mit der Größe des Zugriffs, welche Bytes in der Cache-Zeile adressiert sind.
Normalerweise sind Index und Tag einfache Bitfelder. Eine Speicheradresse sieht also so aus
...Tag... | ...Index... | Offset_within_Cache_Line
(Manchmal sind der Index und das Tag Hashes, z. B. einige XORs anderer Bits in den Mittelbereichsbits, die der Index sind. Viel seltener, manchmal sind der Index und seltener das Tag Dinge wie das Aufnehmen der Cache-Zeilenadresse modulo a Primzahl. Diese komplizierteren Indexberechnungen sind Versuche, das hier erläuterte Resonanzproblem zu bekämpfen. Alle leiden unter irgendeiner Form von Resonanz, aber die einfachsten Bitfeldextraktionsschemata leiden unter Resonanz bei allgemeinen Zugriffsmustern, wie Sie festgestellt haben.)
Also, typische Werte ... es gibt viele verschiedene Modelle von "Opteron Dual Core", und ich sehe hier nichts, was angibt, welches Sie haben. Wählen Sie eines nach dem Zufallsprinzip aus, das neueste Handbuch, das ich auf der AMD-Website, im Bios and Kernel Developer's Guide (BKDG) für 15-Stunden-Modelle der AMD-Familie 00h-0Fh , 12. März 2012, sehe .
(Familie 15h = Bulldozer-Familie, der jüngste High-End-Prozessor - die BKDG erwähnt Dual Core, obwohl ich die Produktnummer nicht genau kenne, die Sie beschreiben. Trotzdem gilt für alle Prozessoren dieselbe Resonanzidee. Es ist nur so, dass die Parameter wie Cache-Größe und Assoziativität etwas variieren können.)
Ab S.33:
Der 15-Stunden-Prozessor der AMD-Familie enthält einen 16-KByte-4-Wege-L1-Datencache mit zwei 128-Bit-Ports. Dies ist ein Durchschreibcache, der bis zu zwei 128-Byte-Ladevorgänge pro Zyklus unterstützt. Es ist in 16 Bänke mit einer Breite von jeweils 16 Bytes unterteilt. [...] In einem Zyklus kann nur ein Ladevorgang von einer bestimmten Bank des L1-Cache ausgeführt werden.
Um zusammenzufassen:
64-Byte-Cache-Zeile => 6 Offset-Bits innerhalb der Cache-Zeile
16KB / 4-Wege => Die Resonanz beträgt 4KB.
Das heißt, die Adressbits 0-5 sind der Cache-Zeilenversatz.
16 KB / 64 KB Cache-Zeilen => 2 ^ 14/2 ^ 6 = 2 ^ 8 = 256 Cache-Zeilen im Cache.
(Bugfix: Ich habe dies ursprünglich als 128 falsch berechnet, dass ich alle Abhängigkeiten behoben habe.)
4-Wege-Assoziativ => 256/4 = 64 Indizes im Cache-Array. Ich (Intel) nenne diese "Sets".
Das heißt, Sie können den Cache als ein Array von 32 Einträgen oder Sätzen betrachten, wobei jeder Eintrag 4 Cache-Zeilen und ihre Tags enthält. (Es ist komplizierter als das, aber das ist okay).
(Übrigens haben die Begriffe "set" und "way" unterschiedliche Definitionen .)
Es gibt 6 Indexbits, Bits 6-11 im einfachsten Schema.
Dies bedeutet, dass alle Cache-Zeilen, die genau dieselben Werte in den Indexbits 6 bis 11 haben, demselben Satz des Caches zugeordnet werden.
Schauen Sie sich jetzt Ihr Programm an.
C[dimension*i+j] += A[dimension*i+k] * B[dimension*k+j];
Schleife k ist die innerste Schleife. Der Basistyp ist doppelt, 8 Bytes. Wenn dimension = 2048, dh 2 KB, sind aufeinanderfolgende Elemente, auf B[dimension*k+j]
die die Schleife zugreift, 2048 * 8 = 16 KB voneinander entfernt. Sie werden alle demselben Satz des L1-Cache zugeordnet - sie haben alle denselben Index im Cache. Dies bedeutet, dass anstelle von 256 Cache-Zeilen im Cache nur 4 zur Verfügung stehen - die "4-Wege-Assoziativität" des Caches.
Dh Sie werden wahrscheinlich alle 4 Iterationen um diese Schleife einen Cache-Fehler bekommen. Nicht gut.
(Eigentlich sind die Dinge etwas komplizierter. Aber das Obige ist ein gutes erstes Verständnis. Die Adressen der Einträge von B, die oben erwähnt wurden, sind eine virtuelle Adresse. Es kann also leicht unterschiedliche physikalische Adressen geben. Darüber hinaus verfügt Bulldozer über einen prädiktiven Cache. Wahrscheinlich werden virtuelle Adressbits verwendet, damit nicht auf eine Übersetzung von virtuellen zu physischen Adressen gewartet werden muss. Auf jeden Fall: Ihr Code hat eine "Resonanz" von 16 KB. Der L1-Datencache hat eine Resonanz von 16 KB. Nicht gut .)]
Wenn Sie die Dimension nur geringfügig ändern, z. B. auf 2048 + 1, werden die Adressen von Array B auf alle Sätze des Caches verteilt. Und Sie erhalten deutlich weniger Cache-Fehler.
Es ist eine ziemlich übliche Optimierung, Ihre Arrays aufzufüllen, z. B. 2048 auf 2049 zu ändern, um diese Resonanz zu vermeiden. "Cache-Blockierung ist jedoch eine noch wichtigere Optimierung. Http://suif.stanford.edu/papers/lam-asplos91.pdf
Neben der Cache-Line-Resonanz gibt es hier noch andere Dinge. Beispielsweise hat der L1-Cache 16 Bänke mit einer Breite von jeweils 16 Bytes. Mit der Dimension = 2048 werden aufeinanderfolgende B-Zugriffe in der inneren Schleife immer an dieselbe Bank gesendet. Sie können also nicht parallel geschaltet werden - und wenn der A-Zugriff zufällig auf dieselbe Bank geht, verlieren Sie.
Ich denke nicht, dass dies so groß ist wie die Cache-Resonanz.
Und ja, möglicherweise gibt es ein Aliasing. Beispielsweise vergleicht der STLF (Store To Load Forwarding-Puffer) möglicherweise nur ein kleines Bitfeld und erhält falsche Übereinstimmungen.
(Wenn Sie darüber nachdenken, ist Resonanz im Cache wie Aliasing, das mit der Verwendung von Bitfeldern zusammenhängt. Resonanz wird durch mehrere Cache-Zeilen verursacht, die denselben Satz abbilden und nicht über diese verteilt sind. Alisaing wird durch Matching basierend auf unvollständiger Adresse verursacht Bits.)
Insgesamt meine Empfehlung zur Abstimmung:
Versuchen Sie, den Cache ohne weitere Analyse zu blockieren. Ich sage das, weil das Blockieren des Caches einfach ist und es sehr wahrscheinlich ist, dass dies alles ist, was Sie tun müssten.
Verwenden Sie danach VTune oder OProf. Oder Cachegrind. Oder ...
Besser noch, verwenden Sie eine gut abgestimmte Bibliotheksroutine, um die Matrixmultiplikation durchzuführen.
O(n^3)
.