Der Cache ist es die Anzahl der Male zu verringern die CPU abgewürgt würde für eine Speicheranforderung warten erfüllt werden (die Speicher Vermeidung Latenz ) und als zweiter Effekt, möglicherweise die Gesamtdatenmenge zu reduzieren , die (Konservieren werden übertragen muss Speicherbandbreite ).
Techniken zur Vermeidung von Speicherabrufverzögerungen sind in der Regel das erste, was in Betracht gezogen werden muss, und helfen manchmal auf lange Sicht. Die begrenzte Speicherbandbreite ist auch ein begrenzender Faktor, insbesondere für Multicores und Multithread-Anwendungen, bei denen viele Threads den Speicherbus verwenden möchten. Eine andere Reihe von Techniken hilft, das letztere Problem anzugehen.
Durch die Verbesserung der räumlichen Lokalität stellen Sie sicher, dass jede Cache-Zeile vollständig verwendet wird, sobald sie einem Cache zugeordnet wurde. Wenn wir uns verschiedene Standard-Benchmarks angesehen haben, haben wir festgestellt, dass ein überraschend großer Teil davon nicht 100% der abgerufenen Cache-Zeilen verwendet, bevor die Cache-Zeilen entfernt werden.
Die Verbesserung der Cache-Zeilenauslastung hilft in dreierlei Hinsicht:
- Es passt tendenziell nützlichere Daten in den Cache, wodurch die effektive Cache-Größe wesentlich erhöht wird.
- Es passt tendenziell nützlichere Daten in dieselbe Cache-Zeile, was die Wahrscheinlichkeit erhöht, dass angeforderte Daten im Cache gefunden werden.
- Dies reduziert die Anforderungen an die Speicherbandbreite, da weniger Abrufe erfolgen.
Übliche Techniken sind:
- Verwenden Sie kleinere Datentypen
- Organisieren Sie Ihre Daten, um Ausrichtungslöcher zu vermeiden (das Sortieren Ihrer Strukturelemente durch Verringern der Größe ist eine Möglichkeit).
- Achten Sie auf den standardmäßigen dynamischen Speicherzuweiser, der beim Aufwärmen zu Lücken führen und Ihre Daten im Speicher verteilen kann.
- Stellen Sie sicher, dass alle benachbarten Daten tatsächlich in den Hot Loops verwendet werden. Andernfalls sollten Sie Datenstrukturen in heiße und kalte Komponenten aufteilen, damit die Hot-Loops heiße Daten verwenden.
- Vermeiden Sie Algorithmen und Datenstrukturen mit unregelmäßigen Zugriffsmustern und bevorzugen Sie lineare Datenstrukturen.
Wir sollten auch beachten, dass es andere Möglichkeiten gibt, die Speicherlatenz zu verbergen, als Caches zu verwenden.
Moderne CPUs verfügen häufig über einen oder mehrere Hardware-Prefetchers . Sie trainieren die Fehler in einem Cache und versuchen, Regelmäßigkeiten zu erkennen. Beispielsweise beginnt der hw-Prefetcher nach einigen Fehlern bei nachfolgenden Cache-Zeilen mit dem Abrufen von Cache-Zeilen in den Cache, um die Anforderungen der Anwendung zu antizipieren. Wenn Sie ein reguläres Zugriffsmuster haben, leistet der Hardware-Prefetcher normalerweise sehr gute Arbeit. Und wenn Ihr Programm keine regulären Zugriffsmuster anzeigt, können Sie die Dinge verbessern, indem Sie selbst Vorabrufanweisungen hinzufügen .
Wenn die Anweisungen so umgruppiert werden, dass diejenigen, die immer im Cache fehlen, nahe beieinander auftreten, kann die CPU diese Abrufe manchmal überlappen, sodass die Anwendung nur einen Latenztreffer aushält ( Parallelität auf Speicherebene ).
Um den Gesamtspeicherbusdruck zu reduzieren, müssen Sie sich mit der sogenannten zeitlichen Lokalität befassen . Dies bedeutet, dass Sie Daten wiederverwenden müssen, solange sie noch nicht aus dem Cache entfernt wurden.
Das Zusammenführen von Schleifen, die dieselben Daten berühren ( Schleifenfusion ), und das Verwenden von Umschreibtechniken, die als Kacheln oder Blockieren bekannt sind, versuchen, diese zusätzlichen Speicherabrufe zu vermeiden.
Obwohl es für diese Übung zum Umschreiben einige Faustregeln gibt, müssen Sie in der Regel die Abhängigkeiten von Schleifendaten sorgfältig berücksichtigen, um sicherzustellen, dass Sie die Semantik des Programms nicht beeinflussen.
Diese Dinge zahlen sich in der Multicore-Welt wirklich aus, in der Sie nach dem Hinzufügen des zweiten Threads normalerweise keine großen Durchsatzverbesserungen feststellen.