2016-09-04 3 views
2

Je suis en train de tester Intel ADX ajouter avec report et ajouter avec débordement à pipeline ajoute sur grands entiers. J'aimerais voir à quoi devrait ressembler la génération de code attendue. De _addcarry_u64 and _addcarryx_u64 with MSVC and ICC, je pensais que ce serait un test approprié:Cas de test pour adcx et 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; 
} 

Quand je l'examinerai generated code from GCC 6.1 en utilisant -O3 et -madx, il révèle addc sérialisé. -O1 et -O2 produit des résultats similaires:

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 

donc je suppose que le cas de test est pas tout à fait frappant la marque, ou je fais quelque chose de mal, ou je me sers quelque chose de mal, ...

Si je suis correctement pars les documents Intel sur _addcarryx_u64, je crois que le code C devrait générer le pipeline. Donc je suppose que je fais quelque chose de mal:

Description de

Ajouter unsigned entiers 64 bits a et b avec unsigned report 8 bits c_in (porte ou drapeau de trop-plein), et mémoriser le résultat 64 bits non signé, et le report dans dst (indicateur de report ou de dépassement).

Comment puis-je générer le pipeline'd ajouter avec carry/ajouter avec trop-plein (adcx/adox)?


J'ai en fait obtenu une 5e génération Core i7 prêt pour le test (notez le drapeau cpu adx):

$ 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

Je pense que ces intrinsèques sont principalement là parce que MSVC ne permet pas l'assemblage en ligne en mode 64 bits. Avec GCC, vous devez utiliser l'assemblage en ligne dans ce cas. En fait, la meilleure façon d'utiliser 'adc 'qui existe depuis des décennies avec GCC est l'assemblage en ligne. C'est sympa d'avoir l'assemblage en ligne en option mais c'est dommage, comme PITA à utiliser dans GCC. –

Répondre

1

Cela ne ressemble comme un bon test cas. Il s'assemble pour corriger le code de travail, n'est-ce pas? Il est utile pour un compilateur de supporter l'intrinsèque dans ce sens, même s'il ne supporte pas encore de faire du code optimal. Cela permet aux gens de commencer à utiliser l'intrinsèque. Ceci est nécessaire pour la compatibilité.

L'année prochaine ou à chaque fois que le support dorsal du compilateur pour adcx/adox sera terminé, le même code sera compilé vers des binaires plus rapides sans modification de source.

Je suppose que c'est ce qui se passe pour gcc.


clang la mise en œuvre 3.8.1 est plus littérale, mais il finit par faire un travail terrible: flag-économie avec SAHF et push/pop eax. See it on Godbolt.

Je pense qu'il y a même un bogue dans la sortie de la source asm, puisque mov eax, ch ne sera pas assemblé. (Contrairement à gcc, clang/LLVM utilise un assembleur intégré et ne passe pas réellement par une représentation textuelle de asm sur le chemin de LLVM IR au code machine). Le désassemblage du code machine montre mov eax,ebp là. Je pense que c'est aussi un bug, car bpl (ou le reste du registre) n'a pas de valeur utile à ce moment-là. Probablement, il voulait mov al, ch ou movzx eax, ch.

+0

Mise à jour: clang3.9 et 4.0 crash sur cette source, clang5.0 le compile raisonnablement. (En utilisant seulement adcx, mais avec suffisamment de déroulement pour activer ILP en sauvegardant/restituant le carry pour chaque chaîne séparément.) –

0

Lorsque GCC sera corrigé pour générer beaucoup mieux de code inline pour add_carryx _..., soyez prudent avec votre code, car la variante de boucle contient une comparaison (modifie les drapeaux C et O de manière similaire à l'instruction secondaire) et un incrément (modifie les drapeaux C et O comme une instruction d'ajout).

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]); 
    } 

Pour cette raison, c1 et c2 dans votre code sera toujours traitées pitifuly (sauvegardés et restaurés dans des registres temporaires à chaque itération de la boucle). Et le code résultant généré par gcc ressemblera toujours à l'assemblage que vous avez fourni, pour de bonnes raisons. Du point de vue de l'exécution, res [i] est une dépendance immédiate entre les 2 instructions add_carryx, les 2 instructions ne sont pas réellement indépendantes et ne bénéficieront pas d'un parallélisme architectural possible dans le processeur. Je comprends que le code n'est qu'un exemple, mais ce ne sera peut-être pas le meilleur exemple à utiliser lorsque gcc sera modifié.

L'addition de 3 nombres en grand arithmétique entier est un problème difficile; la vectorisation aide, et alors il vaut mieux utiliser addcarryx pour gérer les variantes de boucles en parallèle (incrément et comparaison + branchement sur la même variable, encore un autre problème difficile).

+0

clang5.0 déroule la boucle suffisamment pour être utile. (https://godbolt.org/g/2NTfVs) C'est en fait un test intéressant pour que la 2ème chaîne de transport dépende de la première. Mais notez que c'est seulement une dépendance unidirectionnelle: la chaîne 'res [] + = a []' peut précéder la chaîne 'res [] + = b []', ce qui est le cas de clang. (Puis réutilise ces 4 'res []' valeurs alors qu'elles sont encore dans les registres.) –

+0

Bon point que ceci nécessite un déroulement de la boucle pour éviter de sauvegarder/restaurer porter chaque itération (sauf si vous bouclez sans drapeaux, en utilisant 'lea' et' jrcxz ', ou' loop', [mais ceux-ci ne sont malheureusement pas aussi efficaces sauf sur AMD] (https://stackoverflow.com/questions/35742570/why-is-the-loop-instruction-slow-couldnt-intel-have- implement-it-efficient) –

+0

Merci pour le lien vers godbolts En regardant le code différent généré par différents compilateurs, adcx est utilisé comme si c'était adc, et adox n'est pas utilisé.Vous avez raison, en déroulant quelques itérations, le 2 les chaînes de dépendances pourraient être entrelacées, et pushf/popf pourrait être utilisé pour enregistrer/restaurer les deux drapeaux au moment de la variante de boucle ..... – Pierre