Prefetching Beispiele?
Kann mir jemand ein Beispiel geben oder einen link zu einem Beispiel, das verwendet __builtin_prefetch
im GCC (oder nur die asm-Anweisung prefetcht0 im Allgemeinen) um eine erhebliche performance-Vorteil? Insbesondere möchte ich das Beispiel, um die folgenden Kriterien erfüllen:
- Es ist ein einfaches, kleines, in sich geschlossenes Beispiel.
- Entfernen der
__builtin_prefetch
Instruktion resultiert in einem Leistungsabfall. - Austausch der
__builtin_prefetch
Anweisung mit den entsprechenden Speicher-Zugriff führt zu Leistungseinbußen.
Ist, ich will die kürzeste Beispiel zeigt __builtin_prefetch
Durchführung einer Optimierung, die nicht verwaltet werden, ohne dass es.
InformationsquelleAutor Shaun Harker | 2011-09-07
Du musst angemeldet sein, um einen Kommentar abzugeben.
Hier ist ein tatsächlicher code, ich habe gezogen aus einem größeren Projekt. (Sorry, es ist das kürzeste, das ich finden kann, hatte eine deutliche Beschleunigung von prefetching.)
Dieser code führt eine sehr große Daten transponieren.
In diesem Beispiel verwendet die SSE-prefetch-Anweisungen, die möglicherweise das gleiche wie die, die der GCC emittiert.
Um dieses Beispiel auszuführen, müssen Sie zum kompilieren dieses für x64 und mehr als 4 GB Speicher. Sie können es mit einem kleineren datasize, aber es wird zu schnell, zu Zeit.
Wenn ich es mit ENABLE_PREFETCH aktiviert, dies ist die Ausgabe:
Wenn ich es mit ENABLE_PREFETCH deaktiviert, das ist die Ausgabe:
So gibt es eine 13% speedup von prefetching.
EDIT:
Hier einige weitere Ergebnisse:
Mein Rechner ist ein Core i7-920 @ 3,5 GHz. 8 MB L3-cache. Diese 10% speedup ist mehr oder weniger konstant auf 3 anderen Rechnern, die ich getestet habe: Core i7 2600K @ 4.6 GHz und 2 x Xeon X5482 @ 3.2 GHz. Aber ich gebe zu, ich habe nie getestet es auf einem laptop oder einem AMD-Rechner.
Ich habe gerade bearbeitet meine Antwort mit den benchmarks, die auf allen 4 Maschinen, die ich getestet habe. Sie sind alle Intel-desktops/workstations. Also, dass könnte der Grund sein. Ich wollte nicht testen, ob dein 3. Punkt enthält. Es könnte sein, dass das ersetzen es mit einem memory access könnte produzieren das gleiche Ergebnis.
Der Dritte Punkt ist schwierig zu testen, aufgrund der out-of-order Ausführung. Um zum Dritten Punkt zu halten, werden Sie brauchen, um einige 100 - 200 Anweisungen zwischen den laden zu, wenn es tatsächlich gebraucht wird. Eine angehaltene Last blockieren der pipeline nach der re-order-Puffer gefüllt. Aber ein prefetch nicht. Das einzige mal, wenn Sie sehen die Strafe der angehaltene Last ist, wenn man tatsächlich genug Anweisungen überlauf der re-order-Puffer... Wenn Sie ersetzen Sie einfach mein prefetch mit einem normalen laden, der compiler wird vermutlich optimieren, die Last als dead code... (die stillt Ihren letzten Punkt, lol)
Ja, man müsste hinzufügen, eine Art von "dummy" - Sache, die Sie in den Speicher zugreifen und drucken Sie dann Ihren Wert, so dass Sie nicht optimiert, verschenkt-das ist, was ich Tue. Können Sie mir einen link zu Informationen über das, was Sie diskutieren über die festgefahrenen Belastungen und re-order-Puffer? Ich denke, dass könnte mir eine Welt des guten.
InformationsquelleAutor Mysticial
Binäre Suche ist ein einfaches Beispiel, das könnte profitieren von einer ausdrücklichen prefetching. Der Zugriff Muster in eine binäre Suche sieht ziemlich random, um den hardware-prefetcher, also gibt es wenig chance, dass er genau Vorhersagen, was zu Holen.
In diesem Beispiel habe ich prefetch-die beiden möglichen "Mitte" Orte der next-Schleife, iteration, die in der aktuellen iteration. Einer der prefetches wird wahrscheinlich nie benutzt werden, aber der andere (es sei denn, dies wird die Letzte iteration).
Wenn ich kompilieren und ausführen dieses Beispiels mit DO_PREFETCH aktiviert, ich sehe eine Reduktion um 20% Laufzeit:
Beachten Sie, dass wir tun, doppelt so viele L1-cache lädt in den prefetch-version. Wir tatsächlich tun, viel mehr Arbeit, aber das memory access pattern ist freundlich, die pipeline. Dies zeigt auch die vor-und Nachteile. Während dieser block von code schneller läuft, in der isolation, die wir geladen haben, ist es eine Menge von junk in den caches und dies kann mehr Druck auf andere Teile der Anwendung.
InformationsquelleAutor James Scriven
Habe ich gelernt, eine Menge von der ausgezeichnete Antworten @JamesScriven und @Mystisch. Aber Ihre Beispiele geben nur einen bescheidenen Schub - das Ziel dieser Antwort ist ein (ich muss gestehen, etwas künstlichen) Beispiel, wo dieses Verfahren hat eine größere Wirkung (etwa Faktor 4 auf meinem Rechner).
Gibt es drei mögliche bottle-necks für den modernen Architekturen: CPU-Geschwindigkeit, Speicher-Bandbreite und Speicher-Latenz. Dieses Verfahren wird alle über die Verringerung der Latenzzeiten des Speicher-Zugriffe.
In ein perfektes Szenario, wo die Latenz entspricht X Berechnungs-Schritte, hätten wir einen oracle -, was würden Sie uns sagen, welchen Speicher wir würden in X-Berechnungs-Schritte, die prefetching der Daten veröffentlicht werden sollen, und es würde kommen nur in-time-X Kalkulation-Schritte später.
Für eine Menge von algorithmen, die wir sind (fast) in dieser perfekten Welt. Für eine einfache for-Schleife ist es einfach vorauszusagen, welche Daten werden benötigt, X-Schritte weiter. Out-of-order-Ausführung und andere hardware-tricks machen einen sehr guten job hier, das verbergen der Latenzzeiten fast vollständig.
Das ist der Grund, warum es ist solch eine bescheidene Verbesserung für @Mystischen Beispiel: Der prefetcher ist schon ziemlich gut - es gibt einfach nicht viel Raum für Verbesserung. Die Aufgabe ist auch Speicher-gebunden ist, also wohl nicht viel Bandbreite übrig ist - es könnte immer der limitierende Faktor. Ich konnte sehen, am besten um 8% Verbesserung auf meinem Rechner.
Die entscheidende Erkenntnis aus der @JamesScriven Beispiel: weder wir, noch die CPU weiß, den nächsten access-Adresse, bevor die die aktuellen Daten aus dem Speicher geholt - diese Abhängigkeit ist ziemlich wichtig, ansonsten out-of-order-Ausführung würde dazu führen, dass Sie einen Blick vorwärts, und wäre die hardware in der Lage sein, um die prefetch-Daten. Allerdings, denn wir können darüber spekulieren, nur ein Schritt, es ist nicht viel potential. Ich war nicht in der Lage, mehr als 40% auf meinem Rechner.
Also lasst rig den Wettbewerb und bereiten die Daten in einer Weise, dass wir wissen, welche Adresse zugegriffen wird, die in X Schritten, aber es unmöglich machen, für die hardware zu finden, die es sich aufgrund von Abhängigkeiten auf noch nicht abgerufene Daten (siehe das gesamte Programm am Ende der Antwort):
Einige Bemerkungen:
CPU-time+original-latency-time/CPU-time
.Kompilieren und ausführen führt:
einer Geschwindigkeit zwischen 4 und 5.
Auflistung der
prefetch_demp.cpp
:InformationsquelleAutor ead
Vom die Dokumentation:
Ich bin nicht einverstanden, dass dies ist eine schlechte Antwort. Der OP wollte ein einfaches Beispiel (wahrscheinlich wissen, wie es zu benutzen), diese Antworten auf, die.
Ältere CPUs mit weniger smart hardware-prefetching profitierte von software-prefetching in mehr Fällen. Ich denke, auch P4 gewesen wäre schlau genug, um HW-prefetch-sequentiellen Zugriffen, um zusammenhängende Daten, though. Dies ist ein schreckliches Beispiel, weil es einen Fall, wo die extra-prefetch-Anweisungen nur die Dinge verlangsamen. @a3mlord: Der OP wollte eine Leistung gewinnen, nicht nur die syntax.
In diesem Beispiel ist zu kurz, um die Frage zu beantworten.
InformationsquelleAutor wallyk
Prefetching-Daten optimiert werden kann, um die Cache-Line-Größe, die für die meisten modernen 64-bit-Prozessoren, 64 bytes, zum Beispiel pre-load ein uint32_t[16] mit einer Anweisung.
Beispielsweise auf ArmV8 ich entdeckte durch Experimente casting der Speicher Zeiger auf einen uint32_t 4x4-matrix Vektor (das ist 64 bytes groß) halbiert die erforderlichen Anweisungen erforderlich, wie zuvor musste ich erhöhen um 8, wie es war, lade nur die Hälfte der Daten, auch wenn mein Verständnis war, dass es holt eine volle cache-Zeile.
Prefetching-eine uint32_t[32] original-code Beispiel...
Nach...
Aus irgendeinem Grund der Datentyp int für die Adresse index/offset Gaben bessere Leistung. Getestet mit GCC-8 auf Cortex-a53. Mit einem gleichwertig 64-byte-Vektor auf andere Architekturen werden möglicherweise geben die gleiche Leistungsverbesserung, wenn Sie finden, dass es nicht pre-fetching alle Daten, wie in meinem Fall. In meiner Anwendung mit einer million iteration Schleife Leistungssteigerung von 5%, gerade dies zu tun. Es wurden weitere Voraussetzungen für die Verbesserung.
den 128 megabyte "V" memory allocation werden musste, ausgerichtet auf 64 bytes.
Ich hatte auch die C-Operatoren anstelle von Neon-Interna, da Sie erfordern regelmäßige Datentyp Zeiger (in meinem Fall war es
uint32_t *
), da ansonsten der neue gebaut in den prefetch-Methode hatte einen performance-regression.Mein reales Beispiel finden Sie unter https://github.com/rollmeister/veriumMiner/blob/main/algo/scrypt.c in der scrypt_core() und seine interne Funktion, die alle leicht zu Lesen. Die harte Arbeit erfolgt durch GCC8. Allgemeine Verbesserung der Leistung auf 25%.
InformationsquelleAutor Rauli Kumpulainen