Mit jeder neuen Generation von Intel-Prozessoren entstehen neue und immer komplexer werdende Vektoranweisungen. Obwohl die Länge des Vektors (512 Bit) in naher Zukunft nicht zunehmen wird, werden neue Arten von Daten und Arten von Anweisungen erscheinen. Wer kann zum Beispiel auf einen Blick verstehen, was eine solche Eigenart (und die entsprechende Prozessoranweisung) bewirkt?
Bitweise ternäre Logik, die die Fähigkeit bietet, jede Binärfunktion mit drei Operanden zu implementieren; Die spezifische Binärfunktion wird durch den Wert in imm8 angegeben.
__m512i _mm512_mask_ternarylogic_epi32 (__m512i src, __mmask8 k, __m512i a, __m512i b, int imm8) FOR j := 0 to 15 i := j*32 IF k[j] FOR h := 0 to 31 index[2:0] := (src[i+h] << 2) OR (a[i+h] << 1) OR b[i+h] dst[i+h] := imm8[index[2:0]] ENDFOR ELSE dst[i+31:i] := src[i+31:i] FI ENDFOR dst[MAX:512] := 0
OK, sagen wir, wir haben herausgefunden, wie es funktioniert. Die nächste Komplexitätsstufe ist das Debuggen von Code, der solche Eigenheiten intensiv nutzt.
Diejenigen, die regelmäßig Intrinsics verwenden, kennen eine so nützliche Website - den
Intel Intrinsics Guide . Wenn Sie sich genau ansehen, wie es funktioniert, können Sie leicht feststellen, dass das Javascript-Frontend die Datei data-3.xxxml herunterlädt, in der alle Eigenheiten mit ähnlichem Code wie Matlab ausführlich beschrieben werden. (Zum Beispiel die, die ich im Post-Titel kopiert habe.)
Wenn wir jedoch Intrinsics verwenden, um den Code zu beschleunigen, schreiben wir nicht in Matlab, sondern in C und C ++! Vor drei Monaten fragte mich ein Client, ob es in C eine Implementierung von Vektor-Intrinsics zum Debuggen gibt, und ich entschied mich, einen Parser zu schreiben, der den Code aus dem Intrinsics-Handbuch nach C übersetzt. Es stellt sich heraus, dass eine Bibliothek fast alle Intrinsics implementiert, sodass Sie mit einem schrittweisen Debugger hineingehen können ( oder Debug hinzufügen printf).
Beispielsweise wird eine Operation aus einem Post-Titel zu
for (int j = 0; j <= 15; j++) { if (k & (1 << j)) { for (int h = 0; h <= 31; h++) { int index = ((((src_vec[j] & (1 << h)) >> h) << 2) | (((a_vec[j] & (1 << h)) >> h) << 1) | ((b_vec[j] & (1 << h)) >> h)) & 0x7; dst_vec[j] = (dst_vec[j] & ~(1 << h)) | ((((imm8 & (1 << index)) >> index)) << h); } } else { dst_vec[j] = src_vec[j]; } }
Stimmt das, ist das viel verständlicher? Nicht sehr? Nun, ich habe nur eine komplexe Funktion als Beispiel gewählt. Wenn Sie Code mit Intrinsics (z. B. DSP) debuggen, müssen Sie normalerweise sowohl den Algorithmus als auch die Funktionen der einzelnen Anweisungen berücksichtigen. In Anbetracht der Tatsache, dass die Anweisungen mit langen Vektoren arbeiten und DSP-Algorithmen häufig auf seriöser Mathematik basieren, kommt mein Kopf nicht zurecht - es gibt nicht genügend Kurzzeitgedächtnis und Konzentration. Ich vermute, dass ich nicht allein bin - mehrmals dachte ich sogar, ich hätte einen Fehler in der Anleitung gefunden. Dann stellte sich natürlich jedes Mal heraus, dass ich falsch lag, und es funktionierte nicht, einen neuen FDIV-Fehler zu öffnen. Wenn ich in diesen Fällen jedoch Schritt für Schritt innerhalb der Anweisungen debuggen könnte, würde ich sofort verstehen, unter welchen Bedingungen ein Wert in der Komponente meines Vektors erscheint, den ich nicht erwartet hatte.
Kunden sagten mir, dass sie diese Bibliothek verwenden, um einzelne Funktionen mit AVX-512-Intrinsics auf einem Laptop zu debuggen, der nur AVX2 unterstützt. Natürlich ist
Intel SDE dafür viel besser geeignet - weil es alle Befehlssätze äußerst genau imitiert. Ich habe eine Reihe von Unit-Tests (ebenfalls automatisch generiert), die für jedes Intrinsic aus der Bibliothek das Ergebnis seiner Arbeit mit dem Ergebnis der Ausführung der entsprechenden Assembler-Anweisung vergleichen. Wie es sich für Unit-Tests gehört, funktionieren die meisten wie erwartet. Einige Debugging-Eigenschaften mit einem Gleitkomma (doppelte und einfache Genauigkeit) funktionieren jedoch nicht immer zu 100% korrekt. Ich würde sagen, dass es manchmal eine Art
Schnellmathematik ist . Und es gibt verschiedene Rundungsmechanismen! IEE754 hat viele Feinheiten ...
Es gibt noch eine weitere wichtige Funktion bei der Verwendung von Immintrin-Debug anstelle von SDE (die ich in keiner Weise gutheiße, aber nicht stoppen kann). Wenn Sie gcc oder clang mit der Option kompilieren, z. B.
-march = nehalem , geben gcc und clang 512-Bit-Vektoren aus den Funktionen auf dem Stapel aus den Funktionen zurück, und ICC gibt sie weiterhin an ZMM0 zurück. Daher kann der Intel-Compiler in diesem Modus nicht verwendet werden. Und gcc hat eine nützliche Option
-Og , die beim Debuggen hilft, einschließlich beim Debuggen immintrin.
Es gibt mehrere Eigenheiten, deren Hauptaktion darin besteht, beispielsweise den Inhalt des Registers oder der Flags zu ändern. Ich habe solche Anweisungen nicht implementiert. Nun, während mein Parser noch nicht ganz fertig ist, ist die Implementierung von ungefähr 10% der Intrinsics noch nicht verfügbar.
Die Verwendung von immintrin debug ist sehr einfach - Sie müssen die Quelle nicht ändern, müssen jedoch eine bedingte Kompilierung hinzufügen, um immintrin_dbg.h anstelle von immintrin.h im Falle einer Debug-Erstellung einzuschließen.
Sie können es
auf Github herunterladen.