2016-09-04 3 views
2

Ich teste Intel ADX hinzufügen mit tragen und hinzufügen mit Überlauf zu Pipeline fügt auf große Ganzzahlen. Ich würde gerne sehen, wie die Code-Generierung aussehen sollte. Von _addcarry_u64 and _addcarryx_u64 with MSVC and ICC, dachte ich, das ein geeigneter Testfall wäre:Testfall für adcx und adox

#include <stdint.h> 
#include <x86intrin.h> 
#include "immintrin.h" 

int main(int argc, char* argv[]) 
{ 
    #define MAX_ARRAY 100 
    uint8_t c1 = 0, c2 = 0; 
    uint64_t a[MAX_ARRAY]={0}, b[MAX_ARRAY]={0}, res[MAX_ARRAY]; 
    for(unsigned int i=0; i< MAX_ARRAY; i++){ 
     c1 = _addcarryx_u64(c1, res[i], a[i], (unsigned long long int*)&res[i]); 
     c2 = _addcarryx_u64(c2, res[i], b[i], (unsigned long long int*)&res[i]); 
    } 
    return 0; 
} 

Als ich die generated code from GCC 6.1-O3 und -madx verwende, prüfen zeigt es serialisiert addc. -O1 und -O2 erzeugt ähnliche Ergebnisse:

main: 
     subq $688, %rsp 
     xorl %edi, %edi 
     xorl %esi, %esi 
     leaq -120(%rsp), %rdx 
     xorl %ecx, %ecx 
     leaq 680(%rsp), %r8 
.L2: 
     movq (%rdx), %rax 
     addb $-1, %sil 
     adcq %rcx, %rax 
     setc %sil 
     addb $-1, %dil 
     adcq %rcx, %rax 
     setc %dil 
     movq %rax, (%rdx) 
     addq $8, %rdx 
     cmpq %r8, %rdx 
     jne  .L2 
     xorl %eax, %eax 
     addq $688, %rsp 
     ret 

So wird der Testfall vermute ich, nicht ganz die Marke schlagen, oder ich tue etwas falsch, oder ich bin mit etwas falsch, ...

Wenn ich die Dokumente von Intel unter _addcarryx_u64 richtig analysiere, glaube ich, dass der C-Code die Pipeline erzeugen sollte. Also ich vermute ich etwas tue, falsch:

Beschreibung

hinzufügen unsigned 64-Bit ganze Zahlen a und b mit unsigned 8-Bit-Carry-in C_IN (tragen oder Überlauf-Flag), und speichern Sie das vorzeichenlose 64-Bit-Ergebnis in out, und den Übertrag in dst (Übertrag oder Überlauf-Flag).

Wie kann ich die pipeline'd mit Trage hinzufügen generieren/add mit Überlauf (adcx/adox)?


Ich habe tatsächlich eine fünfte für den Test bereit Core i7 Generation (man beachte den adx CPU-Flag):

$ cat /proc/cpuinfo | grep adx 
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush 
dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc 
arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni 
pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 fma cx16 xtpr pdcm pcid sse4_1 
sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 
3dnowprefetch ida arat epb pln pts dtherm tpr_shadow vnmi flexpriority ept vpid fsgsbase 
tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm rdseed adx smap xsaveopt 
... 
+0

Ich denke, dass diese intrinsics meist da sind, weil MSVC Inline-Assembly in 64-Bit-Modus nicht zulässt. Bei GCC wird in diesem Fall die Inline-Montage benötigt. Tatsächlich ist der beste Weg, 'adc' zu verwenden, der seit Jahrzehnten mit GCC zusammenarbeitet, die Inline-Montage. Es ist schön, Inline-Assemblierung als Option zu haben, aber es ist zu schade, dass es wie PITA in GCC verwendet wird. –

Antwort

1

Dies wie ein guter Testfall sieht. Es stellt sich zusammen, um den Arbeitscode zu korrigieren, richtig? Es ist hilfreich für einen Compiler, das Inhärente in diesem Sinne zu unterstützen, auch wenn es noch keinen optimalen Code unterstützt. Es lässt Leute beginnen, das Innere zu benutzen. Dies ist für die Kompatibilität erforderlich.

Nächstes Jahr oder wann auch immer die Backend-Unterstützung des Compilers für adcx/adox erfolgt, wird derselbe Code zu schnelleren Binärdateien kompiliert, ohne dass die Quelle geändert wird.

Ich nehme an, das ist, was für gcc los ist. Flagge Spar mit SAHF und Push/Pop von EAX: macht einen schrecklichen Job


Klirren 3.8.1-Implementierung ist mehr wörtliche, aber es endet. See it on Godbolt.

Ich denke, es gibt sogar einen Fehler in der Asm-Source-Ausgabe, da mov eax, ch nicht zusammenbauen wird. (Im Gegensatz zu gcc verwendet clang/LLVM einen eingebauten Assembler und durchläuft auf dem Weg von LLVM IR zu Maschinencode keine Textdarstellung von asm. Die Demontage des Maschinencodes zeigt mov eax,ebp dort. Ich denke, das ist auch ein Fehler, weil bpl (oder der Rest des Registers) zu diesem Zeitpunkt keinen nützlichen Wert hat. Wahrscheinlich wollte es mov al, ch oder movzx eax, ch.

+0

Update: clang3.9 und 4.0 Absturz auf dieser Quelle, Clang5.0 kompiliert es vernünftig. (Verwenden Sie nur adcx, aber mit genug Abrollen, um ILP zu aktivieren, indem Sie den Übertrag für jede Kette separat speichern/wiederherstellen.) –

0

Wenn GCC behoben wird, um viel besser inline Code für add_carryx _...Seien Sie vorsichtig mit Ihrem Code, weil die Loop-Variante einen Vergleich enthält (modifiziert die C- und O-Flags ähnlich dem Sub-Befehl) und ein Inkrement (modifiziert die C- und O-Flags wie ein add-Befehl).

for(unsigned int i=0; i< MAX_ARRAY; i++){ 
     c1 = _addcarryx_u64(c1, res[i], a[i], (unsigned long long int*)&res[i]); 
     c2 = _addcarryx_u64(c2, res[i], b[i], (unsigned long long int*)&res[i]); 
    } 

Aus diesem Grund c1 und c2 in Ihrem Code immer pitifuly behandelt (gespeichert und bei jedem Schleifendurchlauf in Temp-Register wiederhergestellt) werden. Und der resultierende Code, der von gcc generiert wird, sieht aus guten Gründen immer noch wie die von Ihnen bereitgestellte Assembly aus.

Aus Sicht der Laufzeit ist res [i] eine unmittelbare Abhängigkeit zwischen den 2 add_carryx Anweisungen, die 2 Anweisungen sind nicht wirklich unabhängig und werden nicht von einer möglichen architektonischen Parallelität im Prozessor profitieren.

Ich verstehe, dass der Code nur ein Beispiel ist, aber vielleicht wird es nicht das beste Beispiel sein, wenn gcc geändert wird.

Die Addition von 3 Zahlen in großen Ganzzahlarithmetik ist ein schwieriges Problem; Vektorisierung hilft, und dann verwenden Sie besser adcarryx, um die Loop-Varianten parallel zu behandeln (Inkrementieren und Vergleichen + verzweigen Sie auf dieselbe Variable, ein weiteres schwieriges Problem).

+0

clang5.0 entrollt die Schleife genug, um nützlich zu sein. (https://godbolt.org/g/2NTfVs) Es ist tatsächlich ein interessanter Test, die zweite Übertragskette von der ersten abhängig zu machen. Aber beachte, dass es nur eine unidirektionale Abhängigkeit ist: Die 'res [] + = a []' Kette kann vor der 'res [] + = b []' Kette laufen, was Clam tut. (Dann werden diese 4 'res []' Werte wiederverwendet, solange sie noch in den Registern sind.) –

+0

Gut, dass das Loop-Abrollverfahren zum Speichern/Wiederherstellen bei jeder Iteration benötigt wird (es sei denn, du fährst ohne Flags mit 'lea' und' jrcxz ', oder' loop', [aber diese sind leider nicht so effizient außer AMD] (https://stackoverflow.com/questions/35742570/why-is-the-loop-instruction-slow-couldnt-intel-have- implementiert-it-effizient) –

+0

Vielen Dank für den Link zu godbolts.Andere Code von verschiedenen Compilern erzeugt, adcx wird verwendet, als wäre es adc, und adox wird nicht verwendet.Sie ​​haben recht, mit ein paar Iterationen, die 2 Abhängigkeitsketten können verschachtelt werden, und pushf/popf könnte verwendet werden, um beide Flags zur Schleifenvariationszeit zu speichern/wiederherzustellen. – Pierre