Ist vxorps-Zeroing auf AMD Jaguar / Bulldozer / Zen schneller mit xmm Registern als ymm?

9

AMD-CPUs verarbeiten 256b AVX-Befehle, indem sie in zwei 128-Bit-Operationen dekodieren. z.B. vaddps ymm0, ymm1,ymm1 auf AMD Steamroller dekodiert zu 2 Makro-Operationen, mit dem halben Durchsatz von vaddps xmm0, xmm1,xmm1 .

XOR-Nullstellung ist ein Spezialfall (keine Eingabeabhängigkeit, und auf Jaguar vermeidet zumindest den Konsum einer physischen Registerdateieingabe und ermöglicht es, movdqa aus diesem Register zu eliminieren / umzubenennen, wie Bulldozer selbst bei Nicht-NEROD-Regs immer ausführt. Aber wird es früh genug erkannt, dass vxorps ymm0,ymm0,ymm0 immer noch nur 1 Makro-Datei mit gleicher Leistung wie vxorps xmm0,xmm0,xmm0 dekodiert? (Im Gegensatz zu vxorps ymm3, ymm2,ymm1 )

Oder findet die Erkennung der Unabhängigkeit später statt, nachdem bereits zwei Ups dekodiert wurden? Nutzt Vector Xor-Zeroing auf AMD-CPUs noch einen Ausführungsport? Auf Intel-CPUs benötigt Nehalem einen Port, aber die Sandybridge-Familie behandelt es in der Ausgabe- / Umbenennungsstufe.

Agner Fogs Befehlstabellen listen diesen Spezialfall nicht auf, und sein Microarch Guide erwähnt nicht die Anzahl der UPs.

Dies könnte bedeuten, dass vxorps xmm0,xmm0,xmm0 eine bessere Möglichkeit ist, _mm256_setzero_ps() zu implementieren.

Bei AVX512 speichert _mm512_setzero_ps() außerdem ein Byte, indem es nur ein VEX-codiertes Nullungs-Idiom und nicht, wie möglich, EVEX verwendet. (d. h. für zmm0-15. vxorps xmm31,xmm31,xmm31 würde immer noch einen EVEX benötigen). gcc / clang verwendet zur Zeit Xor-Nullungs-Idiome der von ihnen gewünschten Registerbreite, anstatt immer AVX-128 zu verwenden.

Wird als Fehler 32862 und gcc Fehler 80636 . MSVC verwendet bereits xmm . Noch nicht an die ICC gemeldet, die auch Zmm-Regs für die Nullsetzung von AVX512 verwendet. (Obwohl Intel nicht daran interessiert ist, sich zu ändern, da es momentan keine Vorteile für Intel-CPUs gibt, nur AMD. Wenn sie jemals eine Low-Power-CPU veröffentlichen, die Vektoren in zwei Hälften teilt, könnten sie das tun. Ihr derzeitiges Low-Power-Design (Silvermont) t unterstützen AVX überhaupt, nur SSE4.)

Der einzige mögliche Nachteil, den ich kenne, wenn ich einen AVX-128-Befehl zum Nullsetzen eines 256b-Registers verwende, ist, dass er kein Aufwärmen der 256b-Ausführungseinheiten auf Intel-CPUs auslöst. Möglicherweise einen C- oder C ++ - Hack zu besiegen, der versucht, sie aufzuwärmen.

(256b-Vektorbefehle sind langsamer für die ersten ~ 56k-Zyklen nach dem ersten 256b-Befehl. Siehe Abschnitt Skylake in Agner Fogs Mikroarchiv pdf). Es ist wahrscheinlich in Ordnung, wenn das Aufrufen einer noinline -Funktion, die _mm256_setzero_ps zurückgibt, keine zuverlässige Methode zum Aufwärmen der Ausführungseinheiten ist. (Eine, die immer noch ohne AVX2 funktioniert, und vermeidet alle Lasten (die cache Miss) könnte __m128 onebits = _mm_castsi128_ps(_mm_set1_epi8(0xff));
return _mm256_insertf128_ps(_mm256_castps128_ps256(onebits), onebits) , die zu pcmpeqd xmm0,xmm0,xmm0 / vinsertf128 ymm0,xmm0,1 kompilieren sollte. Das ist immer noch ziemlich trivial für etwas, das Sie einmal aufrufen Aufwärmen (oder warmhalten) der Ausführungseinheiten weit vor einer kritischen Schleife. Und wenn Sie etwas haben wollen, das inline sein kann, benötigen Sie wahrscheinlich inline-asm.

Ich habe keine AMD-Hardware, deshalb kann ich das nicht testen.

Wenn jemand AMD-Hardware hat, aber nicht wissen kann, wie man testet, benutzt man perf-Zähler, um Zyklen zu zählen (und vorzugsweise m-ops oder ups oder was auch immer AMD sie nennt).

Dies ist die NASM / YASM-Quelle, die ich zum Testen kurzer Sequenzen verwende:

%Vor%

Wenn du nicht Linux verwendest, ersetze vielleicht die Sachen nach der Schleife (exit syscall) mit einem ret und rufe die Funktion von einer C main() Funktion auf.

Stellen Sie mit nasm -felf64 vxor-zero.asm && ld -o vxor-zero vxor-zero.o zusammen, um eine statische Binärdatei zu erstellen. (Oder verwenden Sie the asm-link Skript, das ich in einem Q & amp; A über das Zusammenbauen statischer / dynamischer binaries mit / ohne libc gepostet habe.

Beispielausgabe auf einem i7-6700k (Intel Skylake) mit 3,9 GHz. (IDK, warum mein Gerät nur bis zu 3,9 GHz geht, nachdem es ein paar Minuten im Leerlauf war. Turbo bis 4,2 oder 4,4 GHz funktioniert normalerweise direkt nach dem Booten). Da ich Leistungszähler verwende, ist es eigentlich egal, mit welcher Taktfrequenz die Maschine läuft. Es sind keine Lade- / Speicher- oder Code-Cache-Fehler beteiligt, so dass die Anzahl der Kern-Taktzyklen für alles konstant ist, unabhängig davon, wie lange sie sind.

%Vor%

Das + - 0,02% Zeug ist, weil ich perf stat -r4 ausgeführt habe, also lief es meine binäre 4 Mal.

uops_issued_any und uops_retired_retire_slots sind fusionierte Domänen (Frontend-Durchsatzlimit von 4 pro Takt bei Skylake und Bulldozer-Familie). Die Zählungen sind nahezu identisch, da es keine Verzweigungsfehleinschätzungen gibt (was dazu führt, dass spekulativ ausgegebene UPOs verworfen werden, anstatt zurückgezogen zu werden).

uops_executed_thread ist unfused-domains uops (Ausführungsports). Xor-Zeroing benötigt keine auf Intel-CPUs , es sind also nur die Dez- und Branch-Ups, die tatsächlich ausgeführt werden. (Wenn wir die Operanden in vxorps änderten, so dass nicht nur ein Register auf Null gesetzt wurde, z. B. vxorps ymm2, ymm1,ymm0 , um die Ausgabe in ein Register zu schreiben, das das nächste nicht liest, stimmt uops executed mit der fusionierten Domäne überein.Und wir würden sehen, dass die Durchsatzgrenze drei Vxorps pro Takt ist.)

2000M Uploads mit fusionierter Domäne, die in 500-M-Taktzyklen ausgegeben werden, sind 4,0 Ups, die pro Takt ausgegeben werden: Erreichen des theoretischen maximalen Front-End-Durchsatzes. 6 * 250 ist 1500, also stimmen diese Zahlen mit der Skylake-Dekodierung vxorps ymm,ymm,ymm auf 1 fusionierte Domäne überein.

Bei einer anderen Anzahl von Ups in der Schleife sind die Dinge nicht so gut. z.B. Eine 5-UOP-Schleife wurde nur bei 3,75 UPS pro Takt ausgegeben. Ich habe dies absichtlich auf 8 Ups (wenn Vxorps zu einem einzigen Uop dekodiert) gewählt.

Die Ausgabe-Breite von Zen ist 6 Ups pro Zyklus, also kann es mit einer anderen Menge an Abrollen besser sein. (Siehe diese Frage & Antworten ; A für mehr über kurze Schleifen, deren Uop-Zählwert kein Vielfaches der Issue-Breite ist, auf Intel SnB-Familie-Programmen).

    
Peter Cordes 01.05.2017, 01:53
quelle

1 Antwort

11

xor'ing ein ymm-Register mit sich selbst erzeugt zwei Mikro-Ops auf AMD Ryzen, während xor'ing ein xmm-Register mit sich selbst erzeugt nur eine Mikro-Op. Der optimale Weg, ein Ymm-Register zu kopieren, besteht also darin, das entsprechende xmm-Register mit sich selbst zu xern und auf eine implizite Null-Erweiterung zu setzen.

Der einzige Prozessor, der AVX512 heute unterstützt, ist Knights Landing. Es verwendet eine einzige Mikro-Op für das Xoring eines Zmm-Registers. Es ist sehr üblich, eine neue Erweiterung der Vektorgröße durch Teilen in zwei zu handhaben. Dies geschah mit dem Übergang von 64 auf 128 Bit und mit dem Übergang von 128 auf 256 Bit. Es ist mehr als wahrscheinlich, dass einige Prozessoren in der Zukunft (von AMD oder Intel oder einem anderen Hersteller) 512-Bit-Vektoren in zwei 256-Bit-Vektoren oder sogar vier 128-Bit-Vektoren aufteilen werden. Also ist der optimale Weg, um ein Zmm-Register auf Null zu setzen, das X-Bit oder das 128-Bit-Register mit sich selbst zu bilden und sich auf Null-Extension zu verlassen. Und Sie haben Recht, die 128-Bit-VEX-codierte Anweisung ist ein oder zwei Bytes kürzer.

Die meisten Prozessoren erkennen den xor eines Registers mit sich selbst als unabhängig vom vorherigen Wert des Registers.

    
A Fog 03.05.2017, 05:32
quelle