Ich möchte erweitertes REP MOVSB (ERMSB) verwenden, um eine hohe Bandbreite für eine benutzerdefinierte zu erhalten memcpy
.
ERMSB wurde mit der Ivy Bridge-Mikroarchitektur eingeführt. Weitere Informationen finden Sie im Abschnitt "Erweiterter REP MOVSB- und STOSB-Betrieb (ERMSB)" im Intel-Optimierungshandbuch, wenn Sie nicht wissen, was ERMSB ist.
Ich weiß nur, dass ich dies direkt mit der Inline-Montage tun kann. Ich habe die folgende Funktion von https://groups.google.com/forum/#!topic/gnu.gcc.help/-Bmlm_EG_fE erhalten
static inline void *__movsb(void *d, const void *s, size_t n) {
asm volatile ("rep movsb"
: "=D" (d),
"=S" (s),
"=c" (n)
: "0" (d),
"1" (s),
"2" (n)
: "memory");
return d;
}
Wenn ich dies jedoch benutze, ist die Bandbreite viel geringer als bei memcpy
.
Erhält __movsb
15 GB / s und memcpy
26 GB / s mit meinem i7-6700HQ (Skylake) -System, Ubuntu 16.10, DDR4 bei 2400 MHz Dual Channel 32 GB, GCC 6.2.
Warum ist die Bandbreite bei so viel geringer REP MOVSB
? Was kann ich tun, um es zu verbessern?
Hier ist der Code, mit dem ich das getestet habe.
//gcc -O3 -march=native -fopenmp foo.c
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stddef.h>
#include <omp.h>
#include <x86intrin.h>
static inline void *__movsb(void *d, const void *s, size_t n) {
asm volatile ("rep movsb"
: "=D" (d),
"=S" (s),
"=c" (n)
: "0" (d),
"1" (s),
"2" (n)
: "memory");
return d;
}
int main(void) {
int n = 1<<30;
//char *a = malloc(n), *b = malloc(n);
char *a = _mm_malloc(n,4096), *b = _mm_malloc(n,4096);
memset(a,2,n), memset(b,1,n);
__movsb(b,a,n);
printf("%d\n", memcmp(b,a,n));
double dtime;
dtime = -omp_get_wtime();
for(int i=0; i<10; i++) __movsb(b,a,n);
dtime += omp_get_wtime();
printf("dtime %f, %.2f GB/s\n", dtime, 2.0*10*1E-9*n/dtime);
dtime = -omp_get_wtime();
for(int i=0; i<10; i++) memcpy(b,a,n);
dtime += omp_get_wtime();
printf("dtime %f, %.2f GB/s\n", dtime, 2.0*10*1E-9*n/dtime);
}
Der Grund, an dem ich interessiert bin, rep movsb
basiert auf diesen Kommentaren
Beachten Sie, dass Sie auf Ivybridge und Haswell mit zu großen Puffern, die in MLC passen, movntdqa mit rep movsb schlagen können. movntdqa verursacht eine RFO in LLC, rep movsb nicht ... rep movsb ist beim Streaming in den Speicher auf Ivybridge und Haswell erheblich schneller als movntdqa (aber beachten Sie, dass es vor Ivybridge langsam ist!)
Was fehlt / ist nicht optimal in dieser memcpy-Implementierung?
Hier sind meine Ergebnisse auf dem gleichen System von Tinymembnech .
C copy backwards : 7910.6 MB/s (1.4%)
C copy backwards (32 byte blocks) : 7696.6 MB/s (0.9%)
C copy backwards (64 byte blocks) : 7679.5 MB/s (0.7%)
C copy : 8811.0 MB/s (1.2%)
C copy prefetched (32 bytes step) : 9328.4 MB/s (0.5%)
C copy prefetched (64 bytes step) : 9355.1 MB/s (0.6%)
C 2-pass copy : 6474.3 MB/s (1.3%)
C 2-pass copy prefetched (32 bytes step) : 7072.9 MB/s (1.2%)
C 2-pass copy prefetched (64 bytes step) : 7065.2 MB/s (0.8%)
C fill : 14426.0 MB/s (1.5%)
C fill (shuffle within 16 byte blocks) : 14198.0 MB/s (1.1%)
C fill (shuffle within 32 byte blocks) : 14422.0 MB/s (1.7%)
C fill (shuffle within 64 byte blocks) : 14178.3 MB/s (1.0%)
---
standard memcpy : 12784.4 MB/s (1.9%)
standard memset : 30630.3 MB/s (1.1%)
---
MOVSB copy : 8712.0 MB/s (2.0%)
MOVSD copy : 8712.7 MB/s (1.9%)
SSE2 copy : 8952.2 MB/s (0.7%)
SSE2 nontemporal copy : 12538.2 MB/s (0.8%)
SSE2 copy prefetched (32 bytes step) : 9553.6 MB/s (0.8%)
SSE2 copy prefetched (64 bytes step) : 9458.5 MB/s (0.5%)
SSE2 nontemporal copy prefetched (32 bytes step) : 13103.2 MB/s (0.7%)
SSE2 nontemporal copy prefetched (64 bytes step) : 13179.1 MB/s (0.9%)
SSE2 2-pass copy : 7250.6 MB/s (0.7%)
SSE2 2-pass copy prefetched (32 bytes step) : 7437.8 MB/s (0.6%)
SSE2 2-pass copy prefetched (64 bytes step) : 7498.2 MB/s (0.9%)
SSE2 2-pass nontemporal copy : 3776.6 MB/s (1.4%)
SSE2 fill : 14701.3 MB/s (1.6%)
SSE2 nontemporal fill : 34188.3 MB/s (0.8%)
Beachten Sie, dass auf meinem System SSE2 copy prefetched
auch schneller ist als MOVSB copy
.
In meinen ursprünglichen Tests habe ich den Turbo nicht deaktiviert. Ich habe den Turbo deaktiviert und erneut getestet und es scheint keinen großen Unterschied zu machen. Das Ändern der Energieverwaltung macht jedoch einen großen Unterschied.
Wenn ich es tue
sudo cpufreq-set -r -g performance
Ich sehe manchmal über 20 GB / s mit rep movsb
.
mit
sudo cpufreq-set -r -g powersave
Das Beste, was ich sehe, sind ungefähr 17 GB / s. Scheint memcpy
aber nicht empfindlich auf das Power Management zu reagieren.
Ich habe die Frequenz (mit turbostat
) mit und ohne aktiviertem SpeedStep überprüft , mit performance
und mit powersave
Leerlauf, einer 1-Kern-Last und einer 4-Kern-Last. Ich habe Intels MKL-Dichtematrix-Multiplikation ausgeführt, um eine Last zu erstellen und die Anzahl der verwendeten Threads festzulegen OMP_SET_NUM_THREADS
. Hier ist eine Tabelle der Ergebnisse (Zahlen in GHz).
SpeedStep idle 1 core 4 core
powersave OFF 0.8 2.6 2.6
performance OFF 2.6 2.6 2.6
powersave ON 0.8 3.5 3.1
performance ON 3.5 3.5 3.1
Dies zeigt, dass powersave
die CPU auch bei deaktiviertem SpeedStep immer noch auf die Leerlauffrequenz von taktet 0.8 GHz
. Nur mit performance
ohne SpeedStep läuft die CPU mit einer konstanten Frequenz.
Ich habe zB sudo cpufreq-set -r performance
(weil cpufreq-set
es seltsame Ergebnisse lieferte) verwendet, um die Leistungseinstellungen zu ändern. Dies schaltet den Turbo wieder ein, so dass ich den Turbo danach deaktivieren musste.
memcpy()
verwendet AVX NT-Stores. Und sowohl NT-Stores als auch ERMSB verhalten sich schreibkombinierend und sollten daher keine RFOs erfordern. Meine Benchmarks auf meinem eigenen Computer zeigen jedoch, dass meinmemcpy()
ERMSB und mein ERMSB beide 2/3 der Gesamtbandbreite erreichen, wie es Ihrmemcpy()
(aber nicht Ihr ERMSB) getan hat. Daher gibt es eindeutig irgendwo eine zusätzliche Bustransaktion, und es stinkt sehr wie ein RFO.read()
und beimwrite()
Kopieren von Daten in den Benutzerbereich tatsächlich sehr relevant ist: Der Kernel kann (kann) keine SIMD-Register oder SIMD-Code verwenden. Für ein schnelles Memcpy muss es entweder 64-Bit-Lade- / Speicher verwenden, oder in jüngerer Zeit wird es verwendetrep movsb
oderrep rmovd
wenn festgestellt wird, dass sie in der Architektur schnell sind. So profitieren sie von großen Zügen, ohne dass sie explizit verwendetxmm
oderymm
registriert werden müssen.Antworten:
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 :
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 QWORDmov
mit ein wenig Bit-Twiddling besser dran sind, um die nicht verwendeten Bytes zu maskieren ( oder vielleicht mit 2 expliziten Bytes, Wortmov
s, wenn Sie wissen, dass die Größe genau drei ist).Sie sagen weiter:
Dies scheint auf der aktuellen Hardware mit ERMSB sicherlich falsch zu sein, wo
rep movsb
es mindestens genauso schnell oder schneller ist als diemovd
odermovq
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: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 einfachenmov
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, undrep movs
diese intern verwenden.Nachteile
rep movsb
muss eine bestimmte Semantik implementieren, die möglicherweise stärker ist als die zugrunde liegende Softwareanforderung. Verbietet insbesonderememcpy
ü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. Ebensorep 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:
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.rep movs
- aber das ist ein viel kleineres Delta als das von Ihnen angegebene (26 GB / s gegenüber 15 GB / s = ~ 73%).memcpy
), aber aufgrund des obigen Hinweises spielt dies wahrscheinlich keine Rolle.rep movs
Ansätze liegen in der Mitte.rep movsd
scheint die gleiche Magie wierep movsb
auf diesem Chip zu verwenden. Das ist interessant, weil ERMSB nur explizitmovsb
auf frühere Bögen abzielt und frühere Tests mit ERMSB zeigen, dass diemovsb
Leistung viel schneller ist alsmovsd
. Dies ist meistens akademisch, damovsb
es allgemeiner ist alsmovsd
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-zeitlichememcpy
, 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 libcmemcpy
(einschließlich der vom Compiler eingefügten Kopien) oder einer handgerolltenmemcpy
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 integriertememcpy
), 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-Registernmov
basieren und auf x86 eine einfache 64-Bit- basierte Kopie verwendet wird. Für diese Plattformenrep 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. Diesrep 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 dierep 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 ):
Ü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 werdenmemcpy
.Auf diesen Vorteil wird von Intel in ihrem Optimierungshandbuch (Abschnitt 11.16.3.4) und in ihren Worten hingewiesen:
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 derrep 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ährendrep movsb
noch ausgegeben wird, aber nachfolgende Anweisungen können direkt nach dem letztenrep 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 desmemcpy
(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, ummemcpy
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 Plattformmemcpy
. 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:
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 dasmtune
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 vermutlichrep 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 explizitenmov
Fall sehr ähnlich sieht . Es scheint jedoch nicht so zu funktionieren - Uops aus nachfolgenden Anweisungen können sich mit Uops aus dem Mikrocodierten vermischenrep 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).quelle
perf stat
undperf record -e branch-misses:pp
unter Linux (und was auch immer das Äquivalent unter Windows ist) leicht überprüfen .movsd
Vergleich zumovsb
, an einigen Stellen behaupten , sie die gleiche Leistung auf habenerms
Plattformen, aber oben Ich sage , dass frühere Versuche an früheren Archs mit ERMSB zeigenmovsb
als viel schneller durchführenmovsd
. Das ist spezifisch genug, dass ich die Daten gesehen haben muss, aber ich kann sie in diesem Thread nicht finden. Es könnte von einem dieser beiden großen Threads in RWT stammen oder die Beispiele im Intel-Handbuch bilden.rep movsd
(plus ein Nachlaufmovsb
für die letzten drei Bytes) auf Ivy Bridge erheblich schlechter skaliert alsmovsb
bis zu 256 Bytes. An diesem Punkt scheint die Steigung gleich zu sein. Es gibt einige Ivy Bridge Ergebnisse hier , die zeigen ,rep movsd
etwa 3% langsamer alsrep movsb
, aber vielleicht ist das im Rahmen der Messfehler und nicht groß , auch wenn es nicht.Enhanced REP MOVSB (Ivy Bridge und höher) #
Die Ivy Bridge-Mikroarchitektur (Prozessoren, die in den Jahren 2012 und 2013 veröffentlicht wurden) führte Enhanced REP MOVSB ein (wir müssen noch das entsprechende Bit überprüfen) und ermöglichte es uns, den Speicher schnell zu kopieren.
Die billigsten Versionen späterer Prozessoren - Kaby Lake Celeron und Pentium, die 2017 veröffentlicht wurden - verfügen nicht über AVX, das für eine schnelle Speicherkopie hätte verwendet werden können, aber dennoch über das erweiterte REP-MOVSB. Einige der ab 2018 veröffentlichten mobilen und stromsparenden Architekturen von Intel, die nicht auf SkyLake basierten, kopieren mit REP MOVSB etwa zweimal mehr Bytes pro CPU-Zyklus.
REP MOVSB (ERMSB) ist nur dann schneller als eine AVX-Kopie oder eine allgemeine Registerkopie, wenn die Blockgröße mindestens 256 Byte beträgt. Für die Blöcke unter 64 Bytes ist es viel langsamer, da es in ERMSB einen hohen internen Start gibt - ungefähr 35 Zyklen.
Siehe das Intel-Handbuch zur Optimierung, Abschnitt 3.7.6 Erweiterter REP MOVSB- und STOSB-Betrieb (ERMSB) http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia- 32-Architekturen-Optimierungshandbuch.pdf
Wie ich bereits sagte, beginnt REP MOVSB, andere Methoden zu übertreffen, wenn die Länge mindestens 256 Byte beträgt. Um jedoch den klaren Vorteil gegenüber der AVX-Kopie zu erkennen, muss die Länge mehr als 2048 Byte betragen. Es sollte auch beachtet werden, dass die bloße Verwendung von AVX (256-Bit-Register) oder AVX-512 (512-Bit-Register) für die Speicherkopie manchmal schlimme Folgen haben kann, wie z. B. AVX / SSE-Übergangsstrafen oder reduzierte Turbofrequenz. Der REP MOVSB ist also eine sicherere Methode zum Kopieren von Speicher als AVX.
In Bezug auf die Auswirkung der Ausrichtung, wenn REP MOVSB vs. AVX kopiert wird, enthält das Intel-Handbuch die folgenden Informationen:
Ich habe Tests mit Intel Core i5-6600 unter 64-Bit durchgeführt und REP MOVSB memcpy () mit einem einfachen MOV RAX [SRC] verglichen. MOV [DST], RAX-Implementierung, wenn die Daten in den L1-Cache passen :
REP MOVSB memcpy ():
- 1622400000 data blocks of 32 bytes took 17.9337 seconds to copy; 2760.8205 MB/s - 1622400000 data blocks of 64 bytes took 17.8364 seconds to copy; 5551.7463 MB/s - 811200000 data blocks of 128 bytes took 10.8098 seconds to copy; 9160.5659 MB/s - 405600000 data blocks of 256 bytes took 5.8616 seconds to copy; 16893.5527 MB/s - 202800000 data blocks of 512 bytes took 3.9315 seconds to copy; 25187.2976 MB/s - 101400000 data blocks of 1024 bytes took 2.1648 seconds to copy; 45743.4214 MB/s - 50700000 data blocks of 2048 bytes took 1.5301 seconds to copy; 64717.0642 MB/s - 25350000 data blocks of 4096 bytes took 1.3346 seconds to copy; 74198.4030 MB/s - 12675000 data blocks of 8192 bytes took 1.1069 seconds to copy; 89456.2119 MB/s - 6337500 data blocks of 16384 bytes took 1.1120 seconds to copy; 89053.2094 MB/s
MOV RAX ... memcpy ():
- 1622400000 data blocks of 32 bytes took 7.3536 seconds to copy; 6733.0256 MB/s - 1622400000 data blocks of 64 bytes took 10.7727 seconds to copy; 9192.1090 MB/s - 811200000 data blocks of 128 bytes took 8.9408 seconds to copy; 11075.4480 MB/s - 405600000 data blocks of 256 bytes took 8.4956 seconds to copy; 11655.8805 MB/s - 202800000 data blocks of 512 bytes took 9.1032 seconds to copy; 10877.8248 MB/s - 101400000 data blocks of 1024 bytes took 8.2539 seconds to copy; 11997.1185 MB/s - 50700000 data blocks of 2048 bytes took 7.7909 seconds to copy; 12710.1252 MB/s - 25350000 data blocks of 4096 bytes took 7.5992 seconds to copy; 13030.7062 MB/s - 12675000 data blocks of 8192 bytes took 7.4679 seconds to copy; 13259.9384 MB/s
Selbst in 128-Bit-Blöcken ist REP MOVSB langsamer als nur eine einfache MOV RAX-Kopie in einer Schleife (nicht abgewickelt). Die ERMSB-Implementierung beginnt, die MOV-RAX-Schleife nur ab 256-Byte-Blöcken zu übertreffen.
#Normal (nicht erweitert) REP MOVS auf Nehalem und höher #
Überraschenderweise hatten frühere Architekturen (Nehalem und später), die noch kein erweitertes REP-MOVB hatten, eine recht schnelle Implementierung von REP MOVSD / MOVSQ (aber nicht REP MOVSB / MOVSW) für große Blöcke, aber nicht groß genug, um den L1-Cache zu überdimensionieren.
Das Intel Optimization Manual (2.5.6 REP String Enhancement) enthält die folgenden Informationen zur Nehalem-Mikroarchitektur - Intel Core i5-, i7- und Xeon-Prozessoren, die 2009 und 2010 veröffentlicht wurden.
REP MOVSB
Die Latenz für MOVSB beträgt 9 Zyklen, wenn ECX <4; Andernfalls verursachen REP MOVSB mit ECX> 9 Startkosten von 50 Zyklen.
Mein Fazit: REP MOVSB ist auf Nehalem fast nutzlos.
MOVSW / MOVSD / MOVSQ
Zitat aus dem Intel Optimization Manual (2.5.6 REP String Enhancement):
Intel scheint hier nicht richtig zu sein. Aus dem obigen Zitat geht hervor, dass REP MOVSW für sehr große Speicherblöcke genauso schnell ist wie REP MOVSD / MOVSQ. Tests haben jedoch gezeigt, dass nur REP MOVSD / MOVSQ schnell sind, während REP MOVSW auf Nehalem und Westmere sogar langsamer als REP MOVSB ist .
Nach den Informationen von Intel im Handbuch sind die Startkosten bei früheren Intel-Mikroarchitekturen (vor 2008) sogar noch höher.
Fazit: Wenn Sie nur Daten kopieren müssen, die in den L1-Cache passen, sind nur 4 Zyklen zum Kopieren von 64 Datenbytes hervorragend, und Sie müssen keine XMM-Register verwenden!
#REP MOVSD / MOVSQ ist die universelle Lösung, die auf allen Intel-Prozessoren (kein ERMSB erforderlich) hervorragend funktioniert, wenn die Daten in den L1-Cache passen. #
Hier sind die Tests von REP MOVS *, wenn sich die Quelle und das Ziel im L1-Cache befanden, von Blöcken, die groß genug sind, um nicht ernsthaft von den Startkosten betroffen zu sein, aber nicht so groß, dass sie die Größe des L1-Cache überschreiten. Quelle: http://users.atw.hu/instlatx64/
Yonah (2006-2008)
REP MOVSB 10.91 B/c REP MOVSW 10.85 B/c REP MOVSD 11.05 B/c
Nehalem (2009-2010)
REP MOVSB 25.32 B/c REP MOVSW 19.72 B/c REP MOVSD 27.56 B/c REP MOVSQ 27.54 B/c
Westmere (2010-2011)
REP MOVSB 21.14 B/c REP MOVSW 19.11 B/c REP MOVSD 24.27 B/c
Ivy Bridge (2012-2013) - mit erweitertem REP-MOVSB (alle nachfolgenden CPUs verfügen auch über erweitertes REP-MOVSB)
REP MOVSB 28.72 B/c REP MOVSW 19.40 B/c REP MOVSD 27.96 B/c REP MOVSQ 27.89 B/c
SkyLake (2015-2016)
REP MOVSB 57.59 B/c REP MOVSW 58.20 B/c REP MOVSD 58.10 B/c REP MOVSQ 57.59 B/c
Kaby Lake (2016-2017)
REP MOVSB 58.00 B/c REP MOVSW 57.69 B/c REP MOVSD 58.00 B/c REP MOVSQ 57.89 B/c
Cannon Lake, mobil (Mai 2018 - Februar 2020)
REP MOVSB 107.44 B/c REP MOVSW 106.74 B/c REP MOVSD 107.08 B/c REP MOVSQ 107.08 B/c
Cascade Lake, Server (April 2019)
REP MOVSB 58.72 B/c REP MOVSW 58.51 B/c REP MOVSD 58.51 B/c REP MOVSQ 58.20 B/c
Comet Lake, Desktop, Workstation, Mobile (August 2019)
REP MOVSB 58.72 B/c REP MOVSW 58.62 B/c REP MOVSD 58.72 B/c REP MOVSQ 58.72 B/c
Ice Lake, mobil (September 2019)
REP MOVSB 102.40 B/c REP MOVSW 101.14 B/c REP MOVSD 101.14 B/c REP MOVSQ 101.14 B/c
Tremont, geringer Stromverbrauch (September 2020)
REP MOVSB 119.84 B/c REP MOVSW 121.78 B/c REP MOVSD 121.78 B/c REP MOVSQ 121.78 B/c
Tiger Lake, mobil (Oktober 2020)
REP MOVSB 93.27 B/c REP MOVSW 93.09 B/c REP MOVSD 93.09 B/c REP MOVSQ 93.09 B/c
Wie Sie sehen, unterscheidet sich die Implementierung von REP MOVS von Mikroarchitektur zu Mikroarchitektur erheblich. Auf einigen Prozessoren wie Ivy Bridge ist REP MOVSB am schnellsten, wenn auch nur geringfügig schneller als REP MOVSD / MOVSQ, aber ohne Zweifel funktioniert REP MOVSD / MOVSQ auf allen Prozessoren seit Nehalem sehr gut - Sie benötigen nicht einmal "Enhanced REP" MOVSB ", da REP MOVSD auf Ivy Bridge (2013) mit Enhacnced REP MOVSB die gleichen Byte-pro-Uhr-Daten wie auf Nehalem (2010) ohne Enhacnced REP MOVSB anzeigt , während REP MOVSB erst seit SkyLake (2015) sehr schnell wurde. - doppelt so schnell wie auf der Ivy Bridge. Dieses Enhacnced REP MOVSB- Bit in der CPUID kann also verwirrend sein - es zeigt nur, dass es
REP MOVSB
per se in Ordnung ist, aber nicht, dass einesREP MOVS*
schneller ist.Die verwirrendste ERMBSB-Implementierung ist die Ivy Bridge-Mikroarchitektur. Ja, auf sehr alten Prozessoren hat REP MOVS * für große Blöcke vor ERMSB eine Cache-Protokollfunktion verwendet, die für regulären Code nicht verfügbar ist (kein RFO). Dieses Protokoll wird jedoch auf Ivy Bridge mit ERMSB nicht mehr verwendet. Laut Andy Glews Kommentaren zu einer Antwort auf "Warum sind komplizierte Memcpy / Memsets überlegen?" aus einer Antwort von Peter CordesEine Cache-Protokollfunktion, die für regulären Code nicht verfügbar ist, wurde früher auf älteren Prozessoren verwendet, auf Ivy Bridge jedoch nicht mehr. Und es gibt eine Erklärung dafür, warum die Startkosten für REP MOVS * so hoch sind: „Der große Aufwand für die Auswahl und Einrichtung der richtigen Methode ist hauptsächlich auf das Fehlen einer Vorhersage der Mikrocode-Verzweigung zurückzuführen.“ Es gab auch einen interessanten Hinweis, dass Pentium Pro (P6) 1996 REP MOVS * mit 64-Bit-Mikrocode-Lade- und -Speichern und einem No-RFO-Cache-Protokoll implementierte - im Gegensatz zu ERMSB in Ivy Bridge haben sie die Speicherreihenfolge nicht verletzt.
Haftungsausschluss
quelle
rep movs
sollten ein No-RFO-Protokoll verwenden, selbst auf CPUs vor ERMSB.movsb
,movsd
,movsq
führen in etwa gleich auf allen aktuellen Intel - Plattformen. Der interessanteste Imbiss ist wahrscheinlich "nicht verwendenmovsw
". Sie vergleichen sich nicht mit einer expliziten Schleife vonmov
Anweisungen (einschließlich 16-Byte-Verschiebungen auf 64-Bit-Plattformen, die garantiert verfügbar sind), die in vielen Fällen wahrscheinlich schneller sein wird. Sie wissen nicht, was auf AMD-Plattformen passiert und wann die Größe die L1-Größe überschreitet.rep movsb
tatsächlich implementiert wirdmemcpy
(und keiner von ihnen implementiertmemmove
), sodass Sie zusätzlichen Code für die anderen Varianten benötigen. Dies ist wahrscheinlich nur bei kleinen Größen von Bedeutung.stosb
, gilt aber auch für diemov
Varianten). Es ist fraglich, ob dies immer noch "nicht für regulären Code verfügbar" ist, da Sie mit NT-Speichern den gleichen Effekt erzielen. Es ist daher nicht klar, ob "nicht für regulären Code verfügbar" nur NT-Speicher auf Plattformen bedeutet, die dies nicht getan haben haben sie oder etwas anderes als NT-Geschäfte.Sie sagen, dass Sie wollen:
Aber ich bin nicht sicher, ob es bedeutet, was Sie denken, dass es bedeutet. In den 3.7.6.1-Dokumenten, auf die Sie verlinken, heißt es ausdrücklich:
Nur weil dies
CPUID
auf die Unterstützung von ERMSB hinweist, ist dies keine Garantie dafür, dass REP MOVSB der schnellste Weg ist, Speicher zu kopieren. Es bedeutet nur, dass es nicht so schlecht saugt wie in einigen früheren CPUs.Nur weil es Alternativen gibt, die unter bestimmten Bedingungen schneller laufen können, bedeutet dies nicht, dass REP MOVSB nutzlos ist. Nachdem die Leistungseinbußen, die diese Anweisung früher verursacht hat, weg sind, ist sie möglicherweise wieder eine nützliche Anweisung.
Denken Sie daran, es ist ein winziges Stück Code (2 Bytes!) Im Vergleich zu einigen der komplizierteren Memcpy-Routinen, die ich gesehen habe. Da das Laden und Ausführen großer Codestücke auch eine Strafe mit sich bringt (indem Sie einen Teil Ihres anderen Codes aus dem Cache der CPU werfen), wird der „Vorteil“ von AVX et al. Manchmal durch die Auswirkungen auf den Rest Ihres Computers ausgeglichen Code. Kommt darauf an, was du tust.
Sie fragen auch:
Es wird nicht möglich sein, "etwas zu tun", um REP MOVSB schneller laufen zu lassen. Es macht was es macht.
Wenn Sie die höheren Geschwindigkeiten von memcpy sehen möchten, können Sie die Quelle dafür ausgraben. Es ist irgendwo da draußen. Oder Sie können von einem Debugger aus darauf zurückgreifen und die tatsächlich verwendeten Codepfade anzeigen. Ich gehe davon aus, dass einige dieser AVX-Anweisungen verwendet werden, um mit 128 oder 256 Bit gleichzeitig zu arbeiten.
Oder Sie können einfach ... Nun, Sie haben uns gebeten, es nicht zu sagen.
quelle
REP MOVSB
im L3-Cache auf Größen getestet und tatsächlich ist es mit einer SSE / AVX-Lösung konkurrenzfähig. Aber ich habe es noch nicht eindeutig besser gefunden. Und für Größen, die größer als der L3-Cache sind, gewinnen nicht-temporäre Speicher immer noch viel Zeit. Ihr Punkt zur Codegröße ist interessant und erwägenswert. Ich weiß nicht viel über Mikrocode.REP MOVSB
wird mit Mikrocode implementiert, so dass, obwohl es nicht viel des Code-Cache verbraucht und nur als ein Befehl zählt, es dennoch viele der Ports und / oder Mikro-Ops verbrauchen kann.rep movsb
die besser sind als die Alternativen? Ich möchte mehr darüber hören. Zur Verdeutlichung suche ich keine Antwort, die nur zeigt, worep movsb
es für große Arrays besser ist (vielleicht ist das sowieso nicht wahr). Es würde mich interessieren, wo ein Beispielrep movsb
besser ist als Alternativen.memcpy
ist sehr optimiert, alle möglichen verrückten Dinge tut , um die meisten Geschwindigkeit zu bekommen. Wenn Sie die Implementierung Ihrer Bibliothek studieren, werden Sie wahrscheinlich erstaunt sein. (Wenn Sie nicht den Compiler von Microsoft verwenden, werden Sie möglicherweise enttäuscht sein, aber Sie würden diese Frage nicht stellen.) Es ist sehr unwahrscheinlich, dass Sie eine von Hand abgestimmtememcpy
Funktion in Bezug auf Geschwindigkeit übertreffen , und wenn Sie könnten, dann Es ist auch sehr wahrscheinlich, dass die Glibc-Leute bei der Abstimmung auf Ivy Bridge oder eine andere Architektur, die diese Verbesserungen unterstützt, darauf umsteigen würden.Dies ist keine Antwort auf die angegebenen Fragen, sondern nur meine Ergebnisse (und persönlichen Schlussfolgerungen), wenn ich versuche, dies herauszufinden.
Zusammenfassend: GCC optimiert bereits
memset()
/memmove()
/memcpy()
(siehe z. B. gcc / config / i386 / i386.c: expand_set_or_movmem_via_rep () in den GCC-Quellen; suchen Sie auchstringop_algs
in derselben Datei nach architekturabhängigen Varianten). Es gibt also keinen Grund, massive Gewinne durch die Verwendung Ihrer eigenen Variante mit GCC zu erwarten (es sei denn, Sie haben wichtige Dinge wie Ausrichtungsattribute für Ihre ausgerichteten Daten vergessen oder nicht ausreichend spezifische Optimierungen wie aktiviert-O2 -march= -mtune=
). Wenn Sie zustimmen, sind die Antworten auf die angegebene Frage in der Praxis mehr oder weniger irrelevant.(Ich wünschte nur , es war ein
memrepeat()
, das Gegenteil von den immemcpy()
Vergleich zumemmove()
, die den Anfangsteil eines Puffers wiederholen würden den gesamten Puffer zu füllen.)Ich habe derzeit einen Ivy Bridge-Computer im Einsatz (Core i5-6200U-Laptop, Linux 4.4.0 x86-64-Kernel, mit
erms
In-/proc/cpuinfo
Flags). Da ich herausfinden wollte, ob ich einen Fall finden kann, in dem eine benutzerdefinierte memcpy () - Variante, die auf basiertrep movsb
, eine einfache Variante übertreffen würdememcpy()
, habe ich einen übermäßig komplizierten Benchmark geschrieben.Die Kernidee ist, dass das Hauptprogramm drei große Speicherbereiche zuweist:
original
,,current
undcorrect
, jeweils genau gleich groß und mindestens seitenausgerichtet. Die Kopiervorgänge sind in Gruppen zusammengefasst, wobei jede Gruppe unterschiedliche Eigenschaften aufweist, z. B. dass alle Quellen und Ziele ausgerichtet sind (auf eine bestimmte Anzahl von Bytes) oder alle Längen innerhalb desselben Bereichs liegen. Jeder Satz wird beschrieben eine Reihe von unter Verwendung vonsrc
,dst
,n
Triolen, wo allesrc
zusrc+n-1
unddst
zudst+n-1
ist vollständig innerhalb dercurrent
Umgebung.Ein Xorshift * PRNG wird verwendet, um
original
zufällige Daten zu initialisieren . (Wie ich oben gewarnt habe, ist dies zu kompliziert, aber ich wollte sicherstellen, dass ich keine einfachen Verknüpfungen für den Compiler hinterlasse.) Dercorrect
Bereich wird erhalten, indem mitoriginal
Daten in begonnencurrent
wird und alle Triplets im aktuellen Satz unter Verwendung vonmemcpy()
bereitgestellt angewendet werden durch die C-Bibliothek und Kopieren descurrent
Bereichs nachcorrect
. Auf diese Weise kann überprüft werden, ob sich jede Benchmark-Funktion korrekt verhält.Jeder Satz von Kopiervorgängen wird mit derselben Funktion sehr oft zeitgesteuert, und der Median davon wird zum Vergleich verwendet. (Meiner Meinung nach ist der Median beim Benchmarking am sinnvollsten und bietet eine sinnvolle Semantik - die Funktion ist mindestens die Hälfte der Zeit mindestens so schnell.)
Um Compiler-Optimierungen zu vermeiden, lasse das Programm die Funktionen und Benchmarks zur Laufzeit dynamisch laden. Die Funktionen haben alle die gleiche Form.
void function(void *, const void *, size_t)
Beachten Sie, dass sie im Gegensatz zumemcpy()
undmemmove()
nichts zurückgeben. Die Benchmarks (benannte Sätze von Kopiervorgängen) werden dynamisch durch einen Funktionsaufruf generiert (dercurrent
unter anderem den Zeiger auf den Bereich und seine Größe als Parameter verwendet).Leider habe ich noch kein Set gefunden wo
static void rep_movsb(void *dst, const void *src, size_t n) { __asm__ __volatile__ ( "rep movsb\n\t" : "+D" (dst), "+S" (src), "+c" (n) : : "memory" ); }
würde schlagen
static void normal_memcpy(void *dst, const void *src, size_t n) { memcpy(dst, src, n); }
Verwendung
gcc -Wall -O2 -march=ivybridge -mtune=ivybridge
von GCC 5.4.0 auf dem oben genannten Core i5-6200U-Laptop mit einem 64-Bit-Kernel unter Linux-4.4.0. Das Kopieren von Blöcken mit einer Ausrichtung von 4096 Byte und einer Größe kommt jedoch nahe.Dies bedeutet, dass ich zumindest bisher keinen Fall gefunden habe, in dem die Verwendung einer
rep movsb
memcpy-Variante sinnvoll wäre. Dies bedeutet nicht, dass es keinen solchen Fall gibt. Ich habe nur keinen gefunden.(An diesem Punkt ist der Code ein Spaghetti-Durcheinander, auf das ich mich mehr schäme als stolz bin, daher werde ich die Veröffentlichung der Quellen unterlassen, es sei denn, jemand fragt. Die obige Beschreibung sollte jedoch ausreichen, um eine bessere zu schreiben.)
Das überrascht mich allerdings nicht sehr. Der C-Compiler kann viele Informationen über die Ausrichtung der Operandenzeiger ableiten und darüber, ob die Anzahl der zu kopierenden Bytes eine Konstante zur Kompilierungszeit ist, ein Vielfaches einer geeigneten Zweierpotenz. Diese Informationen können und werden / sollten vom Compiler verwendet werden, um die C-Bibliothek
memcpy()
/memmove()
-Funktionen durch eigene zu ersetzen .GCC macht genau das (siehe z. B. gcc / config / i386 / i386.c: expand_set_or_movmem_via_rep () in den GCC-Quellen; suchen Sie auch
stringop_algs
in derselben Datei nach architekturabhängigen Varianten). Tatsächlichmemcpy()
/memset()
/memmove()
wurde schon einige x86 - Prozessor - Varianten bereits separat optimiert; Es würde mich ziemlich überraschen, wenn die GCC-Entwickler die Unterstützung von erms noch nicht aufgenommen hätten.GCC bietet verschiedene Funktionsattribute, mit denen Entwickler einen gut generierten Code sicherstellen können.
alloc_align (n)
Teilt GCC beispielsweise mit, dass die Funktion Speicher zurückgibt, der auf mindestensn
Bytes ausgerichtet ist. Eine Anwendung oder Bibliothek kann auswählen, welche Implementierung einer Funktion zur Laufzeit verwendet werden soll, indem sie eine "Resolver-Funktion" (die einen Funktionszeiger zurückgibt) erstellt und die Funktion mithilfe desifunc (resolver)
Attributs definiert.Eines der häufigsten Muster, die ich in meinem Code dafür verwende, ist
Wo
ptr
ist ein Zeiger,alignment
ist die Anzahl der Bytes, an denen es ausgerichtet ist; GCC weiß / nimmt dann an, dass diespointer
aufalignment
Bytes ausgerichtet ist .Ein weiteres integrierte in nützlich, wenn auch viel schwieriger zu verwenden , richtig ist
__builtin_prefetch()
. Um die Gesamtbandbreite / -effizienz zu maximieren, habe ich festgestellt, dass die Minimierung der Latenzen in jeder Unteroperation die besten Ergebnisse liefert. (Zum Kopieren verstreuter Elemente in einen aufeinanderfolgenden temporären Speicher ist dies schwierig, da das Vorabrufen normalerweise eine vollständige Cache-Zeile umfasst. Wenn zu viele Elemente vorabgerufen werden, wird der größte Teil des Caches durch das Speichern nicht verwendeter Elemente verschwendet.)quelle
Es gibt weitaus effizientere Möglichkeiten, Daten zu verschieben.
memcpy
Heutzutage generiert die Implementierung von architekturspezifischem Code aus dem Compiler, der basierend auf der Speicherausrichtung der Daten und anderen Faktoren optimiert wird. Dies ermöglicht eine bessere Verwendung von nicht-zeitlichen Cache-Anweisungen und XMM und anderen Registern in der x86-Welt.Wenn Sie Hardcode verwenden, wird
rep movsb
diese Verwendung von Intrinsics verhindert.Wenn
memcpy
Sie also etwas schreiben, das an eine bestimmte Hardware gebunden ist, und wenn Sie sich nicht die Zeit nehmen, eine hochoptimiertememcpy
Funktion in der Baugruppe zu schreiben (oder C-Level-Intrinsics verwenden), sind Sie es Es ist weitaus besser, wenn der Compiler es für Sie herausfindet.quelle
memcpy
hier einen Brauch besprochen . Ein Kommentar lautet: "Beachten Sie, dass Sie auf Ivybridge und Haswell mit zu großen Puffern, die in MLC passen, movntdqa mit rep movsb schlagen können; movntdqa verursacht eine RFO in LLC, rep movsb nicht." Ich kann etwas so Gutes bekommen wiememcpy
mitmovntdqa
. Meine Frage ist, wie ich so gut oder besser damitrep movsb
umgehen kann.rep movsd
anscheinend auch schnell . (Auch wenn Sie Recht haben, dass Intel ERMSB nur als Bewerbung fürrep movsdb
/ dokumentiertstosb
)Als allgemeine
memcpy()
Richtlinie:a) Wenn die zu kopierenden Daten winzig sind (weniger als 20 Byte) und eine feste Größe haben, lassen Sie den Compiler dies tun. Grund: Der Compiler kann normale
mov
Anweisungen verwenden und den Startaufwand vermeiden.b) Wenn die zu kopierenden Daten klein sind (weniger als 4 KB) und garantiert ausgerichtet sind, verwenden Sie
rep movsb
(wenn ERMSB unterstützt wird) oderrep movsd
(wenn ERMSB nicht unterstützt wird). Grund: Die Verwendung einer SSE- oder AVX-Alternative hat einen enormen "Startaufwand", bevor etwas kopiert wird.c) Wenn die zu kopierenden Daten klein sind (weniger als etwa 4 KB) und nicht garantiert sind, dass sie ausgerichtet sind, verwenden Sie
rep movsb
. Grund: Die Verwendung von SSE oder AVX oder die Verwendungrep movsd
für den Großteil davon plus einigerep movsb
am Anfang oder Ende hat zu viel Overhead.d) Verwenden Sie für alle anderen Fälle Folgendes:
mov edx,0 .again: pushad .nextByte: pushad popad mov al,[esi] pushad popad mov [edi],al pushad popad inc esi pushad popad inc edi pushad popad loop .nextByte popad inc edx cmp edx,1000 jb .again
Grund: Dies ist so langsam, dass Programmierer gezwungen sind, eine Alternative zu finden, bei der keine großen Datenmengen kopiert werden müssen. und die resultierende Software wird erheblich schneller sein, da das Kopieren großer Datenmengen vermieden wurde.
quelle
rep movsb
sie trotzdem verwendet werden sollte usw. (alle mit potenziellen Fehlvorhersagen für Zweige). Bei den meisten CPUs ist SSE / AVX ausgeschaltet, um Strom zu sparen, wenn Sie es nicht verwenden, sodass Sie von der "SSE / AVX-Einschaltlatenz" betroffen sein können. Dann Overhead für Funktionsaufrufe (zu aufgebläht, um inline zu sein), einschließlich des Speicherns / Wiederherstellens aller SSE / AVX-Register, die vom Anrufer verwendet wurden. Wenn nichts anderes SSE / AVX verwendet, wird der SSE / AVX-Status während des Taskwechsels zusätzlich gespeichert / wiederhergestellt.memcpy_small()
,memcpy_large_unaligned()
,memcpy_large_aligned()
usw. Dies würde dazu beitragen , einige der Startkopf loszuwerden (die Prüfung, etc). Leider sind die Leute eher faul als schlau und (soweit ich das beurteilen kann) macht das eigentlich niemand.rep movsb
undrep movsd
(undrep movsq
) auf neuerer Hardware meistens gleich . Sicher, funktioniertrep movsb
konzeptionell mit Bytes, aber unter dem Deckmantel versuchen alle Anweisungen zum Verschieben von Zeichenfolgen, größere Teile von Bytes zu verschieben, damit sie alle von einer besseren Ausrichtung profitieren (und diese vorteilhafte Ausrichtung beträgt normalerweise 16, 32 oder 64 Bytes, ist also nicht wirklich verwandt zu den primitiven Größen der Operationen). Es ist ähnlich, wiememcpy
Implementierungen im Allgemeinen von der Ausrichtung profitieren, obwohl sie konzeptionell mit Bytes arbeiten.