Betrachten Sie das folgende sehr einfache Computerprogramm:
for i = 1 to n:
y[i] = x[p[i]]
Hier und sind -Glied Arrays von Bytes, und ist eine -Glied Array von Wörtern. Hier ist groß, z. B. (so dass nur ein vernachlässigbarer Teil der Daten in einen beliebigen Cache-Speicher passt).
Angenommen, besteht aus Zufallszahlen , die gleichmäßig zwischen und .
Aus Sicht der modernen Hardware sollte dies Folgendes bedeuten:
- Lesen von ist billig (sequentielles Lesen)
- Lesen ist sehr teuer (random liest, fast alle liest Cache - Misses sind, wir werden jedes einzelne Byte aus dem Hauptspeicher zu holen haben)
- Schreiben ist billig (sequentielles Schreiben).
Und das ist in der Tat, was ich beobachte. Das Programm ist im Vergleich zu einem Programm, das nur sequentielles Lesen und Schreiben ausführt, sehr langsam. Groß.
Nun stellt sich die Frage: Wie gut passt dieses Programm auf moderne Multi-Core-Plattformen?
Meine Hypothese war, dass dieses Programm nicht gut parallelisiert. Schließlich ist der Engpass der Hauptspeicher. Ein einzelner Kern verschwendet bereits die meiste Zeit damit, auf einige Daten aus dem Hauptspeicher zu warten.
Dies war jedoch nicht das, was ich beobachtete, als ich anfing, mit einigen Algorithmen zu experimentieren, bei denen der Engpass eine solche Operation war!
Ich habe einfach die naive for-Schleife durch eine OpenMP-Parallel-for-Schleife ersetzt (im Wesentlichen wird nur der Bereich auf kleinere Teile aufgeteilt und diese Teile auf verschiedenen CPU-Kernen parallel ausgeführt).
Auf Low-End-Computern waren die Beschleunigungen in der Tat gering. Aber auf High-End-Plattformen war ich überrascht, dass ich ausgezeichnete nahezu lineare Beschleunigungen erhielt. Einige konkrete Beispiele (das genaue Timing kann ein bisschen abweichen, es gibt viele zufällige Variationen; dies waren nur schnelle Experimente):
2 x 4-Core-Xeon (insgesamt 8 Kerne): Faktor 5-8-Beschleunigung im Vergleich zur Single-Threaded-Version.
2 x 6-Core Xeon (insgesamt 12 Kerne): Faktor 8-14-Beschleunigung im Vergleich zur Single-Threaded-Version.
Das war völlig unerwartet. Fragen:
Genau warum tut diese Art von Programm parallelisieren so gut ? Was passiert in der Hardware? (Meine derzeitige Vermutung geht in diese Richtung: Die zufälligen Lesevorgänge von verschiedenen Threads sind "pipelined" und die durchschnittliche Rate, mit der Antworten auf diese Fragen erhalten werden, ist viel höher als im Fall eines einzelnen Threads.)
Was ist das richtige theoretische Modell , mit dem wir diese Art von Programmen analysieren (und korrekte Vorhersagen über die Leistung treffen können)?
Bearbeiten: Hier sind nun einige Quellcode- und Benchmark-Ergebnisse verfügbar: https://github.com/suomela/parallel-random-read
- ca. 42 ns pro Iteration (zufälliges Lesen) mit einem einzelnen Thread
- ca. 5 ns pro Iteration (zufälliges Lesen) mit 12 Kernen.
quelle
Ich habe beschlossen, __builtin_prefetch () selbst auszuprobieren. Ich poste es hier als Antwort, falls andere es auf ihren Rechnern testen möchten. Die Ergebnisse liegen in der Nähe dessen, was Jukka beschreibt: Ungefähr 20% weniger Laufzeit beim Vorauslesen von 20 Elementen gegenüber dem Vorauslesen von 0 Elementen.
Ergebnisse:
Code:
quelle
DDR3-Zugang ist in der Tat Pipeline. http://www.eng.utah.edu/~cs7810/pres/dram-cs7810-protocolx2.pdf Die Folien 20 und 24 zeigen, was im Speicherbus während der Pipeline-Leseoperationen passiert.
(teilweise falsch, siehe unten) Mehrere Threads sind nicht erforderlich, wenn die CPU-Architektur den Cache-Prefetch unterstützt. Moderne x86- und ARM-Architekturen sowie viele andere Architekturen verfügen über eine explizite Prefetch-Anweisung. Viele versuchen außerdem, Muster bei Speicherzugriffen zu erkennen und das Prefetching automatisch durchzuführen. Die Softwareunterstützung ist compilerspezifisch, zum Beispiel haben GCC und Clang __builtin_prefech () für das explizite Prefetching.
Hyperthreading im Intel-Stil scheint sehr gut für Programme zu funktionieren, die die meiste Zeit auf Cache-Fehler warten. Nach meiner Erfahrung liegt die Beschleunigung bei einer rechenintensiven Arbeitsbelastung kaum über der Anzahl der physischen Kerne.
EDIT: Ich habe mich in Punkt 2 geirrt. Während Prefetching den Speicherzugriff für einen einzelnen Kern optimieren kann, ist die kombinierte Speicherbandbreite mehrerer Kerne größer als die Bandbreite eines einzelnen Kerns. Wie viel größer, hängt von der CPU ab.
Der Hardware Prefetcher und andere Optimierungen zusammen machen das Benchmarking sehr schwierig. Es ist möglich, Fälle zu konstruieren, in denen explizites Prefetching eine sehr sichtbare oder nicht vorhandene Auswirkung auf die Leistung hat, wobei dieser Maßstab einer der letzteren ist.
quelle