Dies ist ein Thema, das mir sehr am Herzen liegt und das ich kürzlich untersucht habe. Ich werde es daher aus verschiedenen Blickwinkeln betrachten: Geschichte, einige technische Hinweise (meistens akademisch), Testergebnisse auf meiner Box und schließlich der Versuch, Ihre eigentliche Frage zu beantworten wann und wo rep movsb
könnte Sinn machen.
Teilweise ist dies ein Aufruf zum Teilen von Ergebnissen. Wenn Sie Tinymembench ausführen und die Ergebnisse zusammen mit Details Ihrer CPU- und RAM-Konfiguration teilen können, wäre dies großartig. Besonders wenn Sie ein 4-Kanal-Setup, eine Ivy Bridge-Box, eine Server-Box usw. haben.
Geschichte und offizielle Beratung
Die Leistungshistorie der Anweisungen zum schnellen Kopieren von Zeichenfolgen war eine Art Treppenstufen-Angelegenheit - dh Perioden stagnierender Leistung, die sich mit großen Upgrades abwechselten, die sie in Einklang brachten oder sogar schneller als konkurrierende Ansätze. Zum Beispiel gab es einen Leistungssprung in Nehalem (hauptsächlich für Startkosten) und erneut für Ivy Bridge (die meisten zielen auf den Gesamtdurchsatz für große Kopien ab). In diesem Thread finden Sie jahrzehntealte Einblicke in die Schwierigkeiten bei der Implementierung der rep movs
Anweisungen eines Intel-Ingenieurs .
Um zum Beispiel die Einführung von Ivy Bridge, die typisch in Führungen vorhergehenden Rat ist , sie zu vermeiden oder sie benutzen sehr sorgfältig 1 .
Die aktuelle (na ja, Juni 2016) Führung hat eine Vielzahl von verwirrenden und etwas inkonsistent Ratschlägen, wie 2 :
Die spezifische Variante der Implementierung wird zur Ausführungszeit basierend auf Datenlayout, Ausrichtung und dem ECX-Wert (Counter) ausgewählt. Beispielsweise sollte MOVSB / STOSB mit dem REP-Präfix für eine optimale Leistung mit einem Zählerwert kleiner oder gleich drei verwendet werden.
Also für Kopien von 3 oder weniger Bytes? Sie benötigen dafür zunächst kein rep
Präfix, da Sie mit einer behaupteten Startlatenz von ~ 9 Zyklen mit einem einfachen DWORD oder QWORD mov
mit ein wenig Bit-Twiddling besser dran sind, um die nicht verwendeten Bytes zu maskieren ( oder vielleicht mit 2 expliziten Bytes, Wort mov
s, wenn Sie wissen, dass die Größe genau drei ist).
Sie sagen weiter:
String MOVE / STORE-Anweisungen weisen mehrere Datengranularitäten auf. Für eine effiziente Datenverschiebung sind größere Datengranularitäten vorzuziehen. Dies bedeutet, dass eine bessere Effizienz erreicht werden kann, indem ein beliebiger Zählerwert in eine Anzahl von Doppelwörtern plus Einzelbytebewegungen mit einem Zählwert kleiner oder gleich 3 zerlegt wird.
Dies scheint auf der aktuellen Hardware mit ERMSB sicherlich falsch zu sein, wo rep movsb
es mindestens genauso schnell oder schneller ist als die movd
oder movq
Varianten für große Kopien.
Im Allgemeinen enthält dieser Abschnitt (3.7.5) des aktuellen Leitfadens eine Mischung aus vernünftigen und stark veralteten Ratschlägen. Dies ist bei den Intel-Handbüchern üblich, da sie für jede Architektur schrittweise aktualisiert werden (und angeblich auch im aktuellen Handbuch Architekturen im Wert von fast zwei Jahrzehnten abdecken) und alte Abschnitte häufig nicht aktualisiert werden, um sie zu ersetzen oder bedingte Hinweise zu geben das gilt nicht für die aktuelle Architektur.
Anschließend wird ERMSB in Abschnitt 3.7.6 explizit behandelt.
Ich werde den verbleibenden Rat nicht erschöpfend durchgehen, aber ich werde die guten Teile im Abschnitt "Warum verwenden?" Unten zusammenfassen.
Weitere wichtige Aussagen aus dem Handbuch sind, dass Haswell rep movsb
für die interne Verwendung von 256-Bit-Operationen erweitert wurde.
Technische Überlegungen
Dies ist nur eine kurze Zusammenfassung der zugrunde liegenden Vor- und Nachteile, die die rep
Anweisungen vom Standpunkt der Implementierung aus haben .
Vorteile für rep movs
Wenn ein rep
movs-Befehl ausgegeben wird, weiß die CPU , dass ein ganzer Block bekannter Größe übertragen werden soll. Dies kann dazu beitragen, den Betrieb so zu optimieren, dass er mit diskreten Anweisungen nicht möglich ist, zum Beispiel:
- Das Vermeiden der RFO-Anforderung, wenn bekannt ist, dass die gesamte Cache-Zeile überschrieben wird.
- Sofortige und genaue Ausgabe von Prefetch-Anforderungen. Das Hardware-Prefetching macht einen guten Job beim Erkennen von
memcpy
ähnlichen Mustern, aber es dauert immer noch ein paar Lesevorgänge, um zu starten, und es werden viele Cache-Zeilen über das Ende des kopierten Bereichs hinaus "vorabgerufen". rep movsb
kennt genau die Regionsgröße und kann genau vorabrufen.
Anscheinend gibt es keine Garantie für die Bestellung zwischen den Filialen innerhalb von 3 innerhalb einer einzigen, rep movs
was dazu beitragen kann, den Kohärenzverkehr und einfach andere Aspekte der Blockverschiebung zu vereinfachen, im Gegensatz zu einfachen mov
Anweisungen, die einer ziemlich strengen Speicherreihenfolge folgen müssen 4 .
Im Prinzip rep movs
könnte die Anweisung verschiedene Architekturtricks nutzen, die in der ISA nicht verfügbar sind. Beispielsweise können Architekturen breitere interne Datenpfade haben, die der ISA 5 verfügbar macht, und rep movs
diese intern verwenden.
Nachteile
rep movsb
muss eine bestimmte Semantik implementieren, die möglicherweise stärker ist als die zugrunde liegende Softwareanforderung. Verbietet insbesondere memcpy
überlappende Regionen und kann diese Möglichkeit ignorieren, rep movsb
erlaubt sie jedoch und muss das erwartete Ergebnis liefern. Bei aktuellen Implementierungen wirkt sich dies hauptsächlich auf den Startaufwand aus, wahrscheinlich jedoch nicht auf den Durchsatz bei großen Blöcken. Ebenso rep movsb
müssen byte-granulare Kopien unterstützt werden, selbst wenn Sie sie tatsächlich zum Kopieren großer Blöcke verwenden, die ein Vielfaches einer großen Zweierpotenz sind.
Die Software verfügt möglicherweise über Informationen zu Ausrichtung, Kopiergröße und möglichem Aliasing, die bei Verwendung nicht an die Hardware übertragen werden können rep movsb
. Compiler können häufig die Ausrichtung der Speicherblöcke 6 bestimmen und so einen Großteil der Startarbeiten vermeiden, rep movs
die bei jedem Aufruf erforderlich sind .
Testergebnisse
Hier sind die Testergebnisse für viele verschiedene tinymembench
Kopiermethoden auf meinem i7-6700HQ bei 2,6 GHz (schade, dass ich die identische CPU habe, sodass wir keinen neuen Datenpunkt erhalten ...):
C copy backwards : 8284.8 MB/s (0.3%)
C copy backwards (32 byte blocks) : 8273.9 MB/s (0.4%)
C copy backwards (64 byte blocks) : 8321.9 MB/s (0.8%)
C copy : 8863.1 MB/s (0.3%)
C copy prefetched (32 bytes step) : 8900.8 MB/s (0.3%)
C copy prefetched (64 bytes step) : 8817.5 MB/s (0.5%)
C 2-pass copy : 6492.3 MB/s (0.3%)
C 2-pass copy prefetched (32 bytes step) : 6516.0 MB/s (2.4%)
C 2-pass copy prefetched (64 bytes step) : 6520.5 MB/s (1.2%)
---
standard memcpy : 12169.8 MB/s (3.4%)
standard memset : 23479.9 MB/s (4.2%)
---
MOVSB copy : 10197.7 MB/s (1.6%)
MOVSD copy : 10177.6 MB/s (1.6%)
SSE2 copy : 8973.3 MB/s (2.5%)
SSE2 nontemporal copy : 12924.0 MB/s (1.7%)
SSE2 copy prefetched (32 bytes step) : 9014.2 MB/s (2.7%)
SSE2 copy prefetched (64 bytes step) : 8964.5 MB/s (2.3%)
SSE2 nontemporal copy prefetched (32 bytes step) : 11777.2 MB/s (5.6%)
SSE2 nontemporal copy prefetched (64 bytes step) : 11826.8 MB/s (3.2%)
SSE2 2-pass copy : 7529.5 MB/s (1.8%)
SSE2 2-pass copy prefetched (32 bytes step) : 7122.5 MB/s (1.0%)
SSE2 2-pass copy prefetched (64 bytes step) : 7214.9 MB/s (1.4%)
SSE2 2-pass nontemporal copy : 4987.0 MB/s
Einige wichtige Imbissbuden:
- Die
rep movs
Methoden sind schneller als alle anderen Methoden, die nicht "nicht zeitlich" sind 7 , und erheblich schneller als die "C" -Ansätze, bei denen jeweils 8 Bytes kopiert werden.
- Die "nicht-zeitlichen" Methoden sind um bis zu 26% schneller als die anderen
rep movs
- aber das ist ein viel kleineres Delta als das von Ihnen angegebene (26 GB / s gegenüber 15 GB / s = ~ 73%).
- Wenn Sie keine nicht-temporären Speicher verwenden, ist die Verwendung von 8-Byte-Kopien von C genauso gut wie das SSE-Laden / Speichern mit 128 Bit Breite. Dies liegt daran, dass eine gute Kopierschleife genügend Speicherdruck erzeugen kann, um die Bandbreite zu sättigen (z. B. 2,6 GHz * 1 Speicher / Zyklus * 8 Byte = 26 GB / s für Speicher).
- In tinymembench gibt es keine expliziten 256-Bit-Algorithmen (außer wahrscheinlich dem "Standard"
memcpy
), aber aufgrund des obigen Hinweises spielt dies wahrscheinlich keine Rolle.
- Der erhöhte Durchsatz der nicht-zeitlichen Speicheransätze gegenüber den zeitlichen Ansätzen beträgt ungefähr das 1,45-fache, was sehr nahe an dem 1,5-fachen liegt, das Sie erwarten würden, wenn NT 1 von 3 Übertragungen eliminiert (dh 1 Lesen, 1 Schreiben für NT gegen 2 liest, 1 schreibt). Die
rep movs
Ansätze liegen in der Mitte.
- Die Kombination aus relativ geringer Speicherlatenz und bescheidener 2-Kanal-Bandbreite bedeutet, dass dieser spezielle Chip seine Speicherbandbreite von einem einzelnen Thread aus sättigen kann, was das Verhalten dramatisch ändert.
rep movsd
scheint die gleiche Magie wie rep movsb
auf diesem Chip zu verwenden. Das ist interessant, weil ERMSB nur explizit movsb
auf frühere Bögen abzielt und frühere Tests mit ERMSB zeigen, dass die movsb
Leistung viel schneller ist als movsd
. Dies ist meistens akademisch, da movsb
es allgemeiner ist als movsd
sowieso.
Haswell
Wenn wir uns die Haswell-Ergebnisse ansehen, die freundlicherweise von iwillnotexist in den Kommentaren zur Verfügung gestellt wurden, sehen wir dieselben allgemeinen Trends (die relevantesten extrahierten Ergebnisse):
C copy : 6777.8 MB/s (0.4%)
standard memcpy : 10487.3 MB/s (0.5%)
MOVSB copy : 9393.9 MB/s (0.2%)
MOVSD copy : 9155.0 MB/s (1.6%)
SSE2 copy : 6780.5 MB/s (0.4%)
SSE2 nontemporal copy : 10688.2 MB/s (0.3%)
Der rep movsb
Ansatz ist immer noch langsamer als der nicht-zeitliche memcpy
, hier jedoch nur um etwa 14% (im Vergleich zu ~ 26% im Skylake-Test). Der Vorteil der NT-Techniken gegenüber ihren zeitlichen Verwandten liegt jetzt bei ~ 57%, sogar etwas mehr als der theoretische Vorteil der Bandbreitenreduzierung.
Wann sollten Sie verwenden rep movs
?
Zum Schluss noch ein Stich zu Ihrer eigentlichen Frage: Wann oder warum sollten Sie sie verwenden? Es stützt sich auf das oben Gesagte und führt einige neue Ideen ein. Leider gibt es keine einfache Antwort: Sie müssen verschiedene Faktoren abwägen, darunter einige, die Sie wahrscheinlich nicht einmal genau kennen, wie beispielsweise zukünftige Entwicklungen.
Ein Hinweis, dass die Alternative rep movsb
zu der optimierten libc memcpy
(einschließlich der vom Compiler eingefügten Kopien) oder einer handgerollten memcpy
Version sein kann. Einige der folgenden Vorteile gelten nur im Vergleich zu der einen oder anderen dieser Alternativen (z. B. "Einfachheit" hilft gegen eine handgerollte Version, aber nicht gegen integrierte memcpy
), andere gelten für beide.
Einschränkungen der verfügbaren Anweisungen
In einigen Umgebungen sind bestimmte Anweisungen oder die Verwendung bestimmter Register eingeschränkt. Beispielsweise ist im Linux-Kernel die Verwendung von SSE / AVX- oder FP-Registern im Allgemeinen nicht zulässig. Daher können die meisten optimierten memcpy
Varianten nicht verwendet werden, da sie auf SSE- oder AVX-Registern mov
basieren und auf x86 eine einfache 64-Bit- basierte Kopie verwendet wird. Für diese Plattformen rep movsb
ermöglicht die Verwendung den größten Teil der Leistung eines optimierten, memcpy
ohne die Einschränkung des SIMD-Codes zu brechen.
Ein allgemeineres Beispiel könnte Code sein, der auf viele Hardwaregenerationen abzielen muss und der kein hardwarespezifisches Dispatching verwendet (z cpuid
. B. using ). Hier könnten Sie gezwungen sein, nur ältere Befehlssätze zu verwenden, was jegliches AVX usw. ausschließt. Dies rep movsb
könnte hier ein guter Ansatz sein, da es den "versteckten" Zugriff auf breitere Lasten und Speicher ermöglicht, ohne neue Befehle zu verwenden. Wenn Sie auf Hardware vor ERMSB abzielen, müssen Sie jedoch prüfen, ob die rep movsb
Leistung dort akzeptabel ist ...
Zukunftssicherheit
Ein schöner Aspekt davon rep movsb
ist, dass es theoretisch die architektonische Verbesserung zukünftiger Architekturen ohne Quellenänderungen nutzen kann, die explizite Bewegungen nicht können. Als beispielsweise 256-Bit-Datenpfade eingeführt wurden, rep movsb
konnten diese (wie von Intel behauptet) genutzt werden, ohne dass Änderungen an der Software erforderlich waren. Software mit 128-Bit-Verschiebungen (die vor Haswell optimal war) musste geändert und neu kompiliert werden.
Dies ist sowohl ein Vorteil für die Softwarewartung (keine Änderung der Quelle erforderlich) als auch ein Vorteil für vorhandene Binärdateien (keine Notwendigkeit, neue Binärdateien bereitzustellen, um die Verbesserung zu nutzen).
Wie wichtig dies ist, hängt von Ihrem Wartungsmodell ab (z. B. wie oft neue Binärdateien in der Praxis bereitgestellt werden) und es ist sehr schwierig zu beurteilen, wie schnell diese Anweisungen in Zukunft voraussichtlich sein werden. Zumindest ist Intel eine Art Leitfaden für Anwendungen in dieser Richtung, indem es sich zu einer zumindest angemessenen Leistung in der Zukunft verpflichtet ( 15.3.3.6 ):
REP MOVSB und REP STOSB werden auf zukünftigen Prozessoren weiterhin eine recht gute Leistung erbringen.
Überlappung mit nachfolgenden Arbeiten
Dieser Vorteil wird memcpy
natürlich nicht in einem einfachen Benchmark angezeigt, bei dem sich per Definition keine späteren Arbeiten überschneiden müssen. Daher müsste die Höhe des Nutzens in einem realen Szenario sorgfältig gemessen werden. Um den größtmöglichen Vorteil zu erzielen, muss möglicherweise der Code um das System neu organisiert werden memcpy
.
Auf diesen Vorteil wird von Intel in ihrem Optimierungshandbuch (Abschnitt 11.16.3.4) und in ihren Worten hingewiesen:
Wenn bekannt ist, dass die Anzahl mindestens tausend Byte oder mehr beträgt, kann die Verwendung von erweitertem REP MOVSB / STOSB einen weiteren Vorteil bieten, um die Kosten des nicht verbrauchenden Codes zu amortisieren. Die Heuristik kann am Beispiel von Cnt = 4096 und memset () als Beispiel verstanden werden:
• Eine 256-Bit-SIMD-Implementierung von memset () muss 128 Instanzen des 32-Byte-Speicherbetriebs mit VMOVDQA ausgeben / ausführen, bevor die nicht verbrauchenden Befehlssequenzen in den Ruhestand gehen können.
• Eine Instanz von erweitertem REP STOSB mit ECX = 4096 wird als langer Micro-Op-Fluss dekodiert, der von der Hardware bereitgestellt wird, wird jedoch als eine Anweisung zurückgezogen. Es gibt viele store_data-Operationen, die abgeschlossen sein müssen, bevor das Ergebnis von memset () verwendet werden kann. Da der Abschluss der Speicherdatenoperation von der Stilllegung der Programmreihenfolge abgekoppelt ist, kann ein wesentlicher Teil des nicht verbrauchenden Codestreams durch die Ausgabe / Ausführung und Stilllegung verarbeitet werden, was im Wesentlichen kostenlos ist, wenn die nicht verbrauchende Sequenz nicht konkurriert für Speicherpufferressourcen.
Intel sagt also, dass nach einigen Uops der Code danach rep movsb
ausgegeben wurde, aber während viele Geschäfte noch im Flug sind und der rep movsb
gesamte noch nicht in den Ruhestand gegangen ist, können Uops, die Anweisungen befolgen, weitere Fortschritte bei der Außerbetriebnahme erzielen Maschinen als sie könnten, wenn dieser Code nach einer Kopierschleife kam.
Die Uops aus einer expliziten Lade- und Speicherschleife müssen alle in der Programmreihenfolge separat in den Ruhestand versetzt werden. Das muss passieren, um im ROB Platz für folgende Uops zu schaffen.
Es scheint nicht viele detaillierte Informationen darüber zu geben, wie lange mikrocodierte Anweisungen rep movsb
genau funktionieren. Wir wissen nicht genau, wie Mikrocode-Zweige einen anderen Strom von Uops vom Mikrocode-Sequenzer anfordern oder wie sich die Uops zurückziehen. Wenn die einzelnen Uops nicht separat in den Ruhestand gehen müssen, nimmt der gesamte Befehl möglicherweise nur einen Platz im ROB ein?
Wenn das Front-End, das die OoO-Maschinerie speist, eine rep movsb
Anweisung im UOP-Cache sieht , aktiviert es das Microcode Sequencer ROM (MS-ROM), um Mikrocode-Uops in die Warteschlange zu senden, die die Ausgabe- / Umbenennungsphase speist. Es ist wahrscheinlich nicht möglich, dass sich andere Uops damit einmischen und 8 ausgeben / ausführen, während rep movsb
noch ausgegeben wird, aber nachfolgende Anweisungen können direkt nach dem letzten rep movsb
UOP abgerufen / dekodiert und ausgegeben werden, während ein Teil der Kopie noch nicht ausgeführt wurde . Dies ist nur dann nützlich, wenn zumindest ein Teil Ihres nachfolgenden Codes nicht vom Ergebnis des memcpy
(was nicht ungewöhnlich ist) abhängt .
Jetzt ist die Größe dieses Vorteils begrenzt: Sie können höchstens N Befehle (eigentlich Uops) über den langsamen rep movsb
Befehl hinaus ausführen. An diesem Punkt werden Sie stehen bleiben, wobei N die ROB-Größe ist . Bei aktuellen ROB-Größen von ~ 200 (192 bei Haswell, 224 bei Skylake) bedeutet dies einen maximalen Vorteil von ~ 200 Zyklen freier Arbeit für nachfolgenden Code mit einem IPC von 1. In 200 Zyklen können Sie ungefähr 800 Byte bei 10 GB kopieren / s, so dass Sie für Kopien dieser Größe möglicherweise freie Arbeit in der Nähe der Kosten der Kopie erhalten (in gewisser Weise, um die Kopie kostenlos zu machen).
Wenn die Kopiengröße jedoch viel größer wird, nimmt die relative Bedeutung dieser schnell ab (z. B. wenn Sie stattdessen 80 KB kopieren, beträgt die freie Arbeit nur 1% der Kopierkosten). Trotzdem ist es für Kopien von bescheidener Größe ziemlich interessant.
Kopierschleifen blockieren auch die Ausführung nachfolgender Anweisungen nicht vollständig. Intel geht nicht detailliert auf die Größe des Vorteils ein oder darauf, welche Art von Kopien oder umgebendem Code den größten Nutzen bringt. (Heißes oder kaltes Ziel oder Quelle, Code mit hohem ILP oder niedrigem ILP und hoher Latenz nach).
Codegröße
Die ausgeführte Codegröße (einige Bytes) ist im Vergleich zu einer typischen optimierten memcpy
Routine mikroskopisch . Wenn die Leistung durch i-Cache-Fehler (einschließlich UOP-Cache) eingeschränkt wird, kann die reduzierte Codegröße von Vorteil sein.
Auch hier können wir die Größe dieses Vorteils anhand der Größe der Kopie begrenzen. Ich werde es nicht numerisch herausarbeiten, aber die Intuition ist, dass das Reduzieren der dynamischen Codegröße um B Bytes höchstens C * B
Cache-Misses für eine Konstante C einsparen kann . Jeder Aufruf, um memcpy
die Cache-Miss-Kosten (oder den Nutzen) einmal zu verursachen, Der Vorteil eines höheren Durchsatzes hängt jedoch von der Anzahl der kopierten Bytes ab. Bei großen Übertragungen dominiert daher ein höherer Durchsatz die Cache-Effekte.
Auch dies wird nicht in einem einfachen Benchmark angezeigt, bei dem die gesamte Schleife zweifellos in den UOP-Cache passt. Sie benötigen einen realen In-Place-Test, um diesen Effekt zu bewerten.
Architekturspezifische Optimierung
Sie haben berichtet, dass Ihre Hardware rep movsb
erheblich langsamer war als die Plattform memcpy
. Aber auch hier gibt es Berichte über das gegenteilige Ergebnis auf früherer Hardware (wie Ivy Bridge).
Das ist durchaus plausibel, da es den Anschein hat, dass die String-Move-Operationen in regelmäßigen Abständen geliebt werden - aber nicht in jeder Generation. Daher kann es sein, dass sie schneller oder zumindest an die Architekturen gebunden sind (an diesem Punkt kann sie aufgrund anderer Vorteile gewinnen) auf den neuesten Stand gebracht, nur um in der nachfolgenden Hardware ins Hintertreffen zu geraten.
Zitat von Andy Glew, der ein oder zwei Dinge darüber wissen sollte, nachdem er diese auf dem P6 implementiert hat:
Die große Schwäche, schnelle Zeichenfolgen im Mikrocode zu erstellen, war, [...] dass der Mikrocode mit jeder Generation verstimmt war und immer langsamer wurde, bis jemand dazu kam, ihn zu reparieren. Genau wie eine Bibliothek fällt eine Männerkopie verstimmt. Ich nehme an, dass es möglich ist, dass eine der verpassten Möglichkeiten darin bestand, 128-Bit-Ladevorgänge und -Speicher zu verwenden, wenn sie verfügbar wurden, und so weiter.
In diesem Fall kann es als eine weitere "plattformspezifische" Optimierung angesehen werden, die in den typischen All-Trick-in-the-Book- memcpy
Routinen angewendet wird, die Sie in Standardbibliotheken und JIT-Compilern finden: jedoch nur für Architekturen, bei denen dies besser ist . Für JIT- oder AOT-kompilierte Inhalte ist dies einfach, für statisch kompilierte Binärdateien ist jedoch ein plattformspezifischer Versand erforderlich, der jedoch häufig bereits vorhanden ist (manchmal zur Verbindungszeit implementiert), oder das mtune
Argument kann verwendet werden, um eine statische Entscheidung zu treffen.
Einfachheit
Selbst auf Skylake, wo es den Anschein hat, als sei es hinter den absolut schnellsten nicht-zeitlichen Techniken zurückgefallen, ist es immer noch schneller als die meisten Ansätze und sehr einfach . Dies bedeutet weniger Zeit für die Validierung, weniger mysteriöse Fehler, weniger Zeit für die Optimierung und Aktualisierung einer Monster- memcpy
Implementierung (oder umgekehrt weniger Abhängigkeit von den Launen der Standard-Bibliotheksimplementierer, wenn Sie sich darauf verlassen).
Latenzgebundene Plattformen
Speicherdurchsatzgebundene Algorithmen 9 können tatsächlich in zwei Hauptregimen arbeiten: DRAM-Bandbreitengebunden oder Parallelitäts- / Latenzzeitgebunden.
Der erste Modus ist der, mit dem Sie wahrscheinlich vertraut sind: Das DRAM-Subsystem verfügt über eine bestimmte theoretische Bandbreite, die Sie anhand der Anzahl der Kanäle, der Datenrate / -breite und der Frequenz recht einfach berechnen können. Zum Beispiel hat mein DDR4-2133-System mit 2 Kanälen eine maximale Bandbreite von 2,133 * 8 * 2 = 34,1 GB / s, wie in ARK angegeben .
Sie werden nicht mehr als diese Rate von DRAM (und normalerweise etwas weniger aufgrund verschiedener Ineffizienzen) aufrechterhalten, die über alle Kerne auf dem Socket hinzugefügt werden (dh es ist eine globale Grenze für Single-Socket-Systeme).
Die andere Grenze wird dadurch festgelegt, wie viele gleichzeitige Anforderungen ein Kern tatsächlich an das Speichersubsystem senden kann. Stellen Sie sich vor, ein Kern könnte nur eine Anforderung gleichzeitig für eine 64-Byte-Cache-Zeile ausführen. Wenn die Anforderung abgeschlossen ist, können Sie eine weitere ausgeben. Nehmen Sie auch eine sehr schnelle Speicherlatenz von 50 ns an. Dann würden Sie trotz der großen DRAM-Bandbreite von 34,1 GB / s nur 64 Bytes / 50 ns = 1,28 GB / s oder weniger als 4% der maximalen Bandbreite erhalten.
In der Praxis können Kerne mehr als eine Anforderung gleichzeitig ausgeben, jedoch nicht eine unbegrenzte Anzahl. Es versteht sich normalerweise, dass zwischen dem L1 und dem Rest der Speicherhierarchie nur 10 Zeilenfüllpuffer pro Kern und zwischen L2 und dem DRAM etwa 16 Füllpuffer vorhanden sind. Das Prefetching konkurriert um dieselben Ressourcen, trägt jedoch zumindest dazu bei, die effektive Latenz zu verringern. Weitere Informationen finden Sie in den großartigen Beiträgen, die Dr. Bandwidth zu diesem Thema verfasst hat , hauptsächlich in den Intel-Foren.
Trotzdem die meisten sind jüngste CPUs durch begrenzt diesen Faktor, nicht die RAM - Bandbreite. Normalerweise erreichen sie 12 bis 20 GB / s pro Kern, während die RAM-Bandbreite 50+ GB / s betragen kann (auf einem 4-Kanal-System). Nur einige neuere 2-Kanal- "Client" -Kerne der letzten Generation, die einen besseren Uncore zu haben scheinen, können möglicherweise mehr Zeilenpuffer die DRAM-Grenze für einen einzelnen Kern erreichen, und unsere Skylake-Chips scheinen einer von ihnen zu sein.
Jetzt gibt es natürlich einen Grund, warum Intel Systeme mit einer DRAM-Bandbreite von 50 GB / s entwirft, während aufgrund von Parallelitätsbeschränkungen nur <20 GB / s pro Kern aufrechterhalten werden sollen: Die erstere Grenze ist sockelweit und die letztere ist pro Kern. Jeder Core auf einem 8-Core-System kann also Anforderungen im Wert von 20 GB / s senden. Ab diesem Zeitpunkt sind sie wieder DRAM-begrenzt.
Warum mache ich das immer weiter? Da die beste memcpy
Implementierung häufig davon abhängt, in welchem Regime Sie arbeiten. Sobald Sie DRAM BW-begrenzt sind (wie unsere Chips anscheinend, aber die meisten nicht auf einem einzelnen Kern), wird die Verwendung von nicht-zeitlichen Schreibvorgängen sehr wichtig, da dies das spart Read-for-Ownership, das normalerweise 1/3 Ihrer Bandbreite verschwendet. Sie sehen das genau in den obigen Testergebnissen: Die memcpy-Implementierungen, die keine NT-Speicher verwenden, verlieren 1/3 ihrer Bandbreite.
Wenn Sie jedoch auf Parallelität beschränkt sind, gleicht sich die Situation aus und kehrt sich manchmal um. Sie haben DRAM-Bandbreite zur Verfügung, sodass NT-Speicher nicht helfen und sogar Schaden anrichten können, da sie die Latenz erhöhen können, da die Übergabezeit für den Leitungspuffer möglicherweise länger ist als in einem Szenario, in dem der Vorabruf die RFO-Leitung in LLC (oder sogar) bringt L2) und dann wird der Speicher in LLC für eine effektiv geringere Latenz abgeschlossen. Schließlich haben Server- Uncores tendenziell viel langsamere NT-Speicher als Client-Speicher (und eine hohe Bandbreite), was diesen Effekt verstärkt.
Auf anderen Plattformen stellen Sie möglicherweise fest, dass NT-Stores weniger nützlich sind (zumindest, wenn Sie sich für Single-Threaded-Leistung interessieren) und vielleicht rep movsb
dort gewinnen (wenn sie das Beste aus beiden Welten bieten ).
Wirklich, dieser letzte Punkt ist ein Aufruf für die meisten Tests. Ich weiß, dass NT-Speicher ihren offensichtlichen Vorteil für Single-Threaded-Tests auf den meisten Bögen (einschließlich der aktuellen Server-Bögen) verlieren, aber ich weiß nicht, wie rep movsb
die Leistung relativ ...
Verweise
Andere gute Informationsquellen, die oben nicht integriert sind.
comp.arch Untersuchung von rep movsb
versus Alternativen. Viele gute Hinweise zur Verzweigungsvorhersage und eine Implementierung des Ansatzes, den ich oft für kleine Blöcke vorgeschlagen habe: Verwenden Sie überlappende erste und / oder letzte Lese- / Schreibvorgänge, anstatt zu versuchen, nur genau die erforderliche Anzahl von Bytes zu schreiben (z. B. Implementierung alle Kopien von 9 bis 16 Bytes als zwei 8-Byte-Kopien, die sich in bis zu 7 Bytes überlappen können).
1 Vermutlich soll es auf Fälle beschränkt werden, in denen beispielsweise die Codegröße sehr wichtig ist.
2 Siehe Abschnitt 3.7.5: REP-Präfix und Datenverschiebung.
3 Es ist wichtig zu beachten, dass dies nur für die verschiedenen Geschäfte innerhalb der einzelnen Anweisung selbst gilt: Sobald der Vorgang abgeschlossen ist, wird der Geschäftsblock in Bezug auf vorherige und nachfolgende Geschäfte weiterhin geordnet angezeigt. Code kann also Geschäfte von der rep movs
Reihenfolge aus in Bezug zueinander anzeigen, jedoch nicht in Bezug auf vorherige oder nachfolgende Geschäfte (und es ist die letztere Garantie, die Sie normalerweise benötigen). Dies ist nur dann ein Problem, wenn Sie das Ende des Kopierziels als Synchronisationsflag anstelle eines separaten Speichers verwenden.
4 Beachten Sie, dass nicht-zeitliche diskrete Speicher auch die meisten Bestellanforderungen vermeiden, obwohl sie in der Praxis rep movs
noch mehr Freiheit bieten, da für WC / NT-Speicher noch einige Bestellbeschränkungen bestehen.
5 Dies war im letzten Teil der 32-Bit-Ära üblich, als viele Chips 64-Bit-Datenpfade hatten (z. B. zur Unterstützung von FPUs, die den 64-Bit- double
Typ unterstützten). Heutzutage haben "kastrierte" Chips wie die Marken Pentium oder Celeron AVX deaktiviert, aber vermutlich rep movs
kann der Mikrocode immer noch 256b Lasten / Speicher verwenden.
6 Zum Beispiel aufgrund von Sprachausrichtungsregeln, Ausrichtungsattributen oder Operatoren, Aliasing-Regeln oder anderen Informationen, die zur Kompilierungszeit festgelegt wurden. Im Falle einer Ausrichtung können sie, selbst wenn die genaue Ausrichtung nicht bestimmt werden kann, zumindest Ausrichtungsprüfungen aus Schleifen herausheben oder auf andere Weise redundante Prüfungen eliminieren.
7 Ich gehe davon aus, dass "Standard" memcpy
einen nicht-zeitlichen Ansatz wählt, der für diese Puffergröße sehr wahrscheinlich ist.
8 Das ist nicht unbedingt offensichtlich, da es der Fall sein könnte, dass der UOP-Stream, der durch den rep movsb
einfach monopolisierten Versand generiert wird, und dann dem expliziten mov
Fall sehr ähnlich sieht . Es scheint jedoch nicht so zu funktionieren - Uops aus nachfolgenden Anweisungen können sich mit Uops aus dem Mikrocodierten vermischen rep movsb
.
9 Das heißt, diejenigen, die eine große Anzahl unabhängiger Speicheranforderungen ausgeben und damit die verfügbare DRAM-zu-Kern-Bandbreite sättigen können, von denen memcpy
ein Aushängeschild wäre (und für rein latenzgebundene Lasten wie Zeigerjagd gilt).