Redondance du code d’assemblage dans le code C optimisé

J’essaie d’apprendre la vectorisation en étudiant le code C simple compilé dans gcc avec l’optimisation -O3. Plus précisément, dans quelle mesure les compilateurs vectorisent. Il s’agit d’un parcours personnel vers la vérification des performances de gcc -O3 avec des calculs plus complexes. Je comprends que la sagesse conventionnelle est que les compilateurs sont meilleurs que les gens, mais je ne prends jamais une telle sagesse pour acquise.

Dans mon premier test simple, cependant, je trouve que certains des choix que gcc fait sont assez étranges et, très honnêtement, d’une négligence flagrante en termes d’optimisation. Je suis prêt à supposer que le compilateur a un objective précis et qu’il connaît quelque chose à propos du processeur (Intel i5-2557M dans ce cas) que je ne connais pas. Mais il me faut une confirmation de la part de personnes bien informées.

Mon code de test simple (segment) est:

int i; float a[100]; for (i=0;i<100;i++) a[i]= (float) i*i; 

Le code d’assemblage résultant (segment) qui correspond à la boucle for est le suivant:

 .L6: ; loop starts here movdqa xmm0, xmm1 ; copy packed integers in xmm1 to xmm0 .L3: movdqa xmm1, xmm0 ; wait, what!? WHY!? this is redundant. cvtdq2ps xmm0, xmm0 ; convert integers to float add rax, 16 ; increment memory pointer for next iteration mulps xmm0, xmm0 ; pack square all integers in xmm0 paddd xmm1, xmm2 ; pack increment all integers by 4 movaps XMMWORD PTR [rax-16], xmm0 ; store result cmp rax, rdx ; test loop termination jne .L6 

Je comprends toutes les étapes et, d’un sharepoint vue calcul, tout cela a du sens. Ce que je ne comprends pas, c’est que gcc a choisi d’incorporer à la boucle itérative une étape permettant de charger xmm1 avec xmm0 juste après que xmm0 ait été chargé avec xmm1 . c’est à dire

  .L6 movdqa xmm0, xmm1 ; loop starts here .L3 movdqa xmm1, xmm0 ; grrr! 

Cela seul me fait douter de la santé de l’optimiseur. De toute évidence, le MOVDQA supplémentaire ne perturbe pas les données, mais à première vue, il semblerait que grossièrement négligent de la part de gcc .

Plus tôt dans le code d’assemblage (non représenté), xmm0 et xmm2 sont initialisés à une valeur significative pour la vectorisation; il est donc évident qu’au début de la boucle, le code doit ignorer le premier MOVDQA. Mais pourquoi ne pas simplement réorganiser gcc , comme indiqué ci-dessous.

 .L3 movdqa xmm1, xmm0 ; initialize xmm1 PRIOR to loop .L6 movdqa xmm0, xmm1 ; loop starts here 

Ou encore mieux, initialisez simplement xmm1 au lieu de xmm0 et supprimez le pas MOVDQA xmm1 , xmm0 !

Je suis prêt à croire que le processeur est suffisamment intelligent pour ignorer l’étape redondante ou quelque chose du genre, mais comment puis-je faire confiance à gcc pour optimiser pleinement le code complexe, s’il peut même obtenir ce code simple, non? Ou quelqu’un peut-il fournir une explication solide qui me ferait croire que gcc -O3 est une bonne chose?

Je ne suis pas sûr à 100%, mais il semble que votre boucle détruit xmm0 en la convertissant en float . Vous devez donc avoir la valeur entière dans xmm1 , puis la copier dans un autre registre (dans ce cas, xmm0 ).

Bien que les compilateurs soient connus pour donner parfois des instructions inutiles, je ne vois pas vraiment comment cela se produit dans ce cas.

Si vous souhaitez que xmm0 (ou xmm1 ) rest un entier, n’utilisez pas de xmm1 de float pour la première valeur de i . Peut-être que ce que tu voulais faire c’est:

  for (i=0;i<100;i++) a[i]= (float)(i*i); 

Par contre, gcc 4.9.2 ne semble pas faire cela:

 g++ -S -O3 floop.cpp .L2: cvtdq2ps %xmm1, %xmm0 mulps %xmm0, %xmm0 addq $16, %rax paddd %xmm2, %xmm1 movaps %xmm0, -16(%rax) cmpq %rbp, %rax jne .L2 

Nor ne clang (3.7.0 il y a environ 3 semaines)

  clang++ -S -O3 floop.cpp movdqa .LCPI0_0(%rip), %xmm0 # xmm0 = [0,1,2,3] xorl %eax, %eax .align 16, 0x90 .LBB0_1: # %vector.body # =>This Inner Loop Header: Depth=1 movd %eax, %xmm1 pshufd $0, %xmm1, %xmm1 # xmm1 = xmm1[0,0,0,0] paddd %xmm0, %xmm1 cvtdq2ps %xmm1, %xmm1 mulps %xmm1, %xmm1 movaps %xmm1, (%rsp,%rax,4) addq $4, %rax cmpq $100, %rax jne .LBB0_1 

Code que j'ai compilé:

 extern int printf(const char *, ...); int main() { int i; float a[100]; for (i=0;i<100;i++) a[i]= (float) i*i; for (i=0; i < 100; i++) printf("%f\n", a[i]); } 

(J'ai ajouté le printf pour éviter que le compilateur se débarrasse de TOUT le code)