optimisation SSE pour du code C (auto ou inline) [résolu]

optimisation SSE pour du code C (auto ou inline) [résolu] - C - Programmation

Marsh Posté le 12-03-2012 à 08:28:11    

Hello
 
Je me suis fait une petite routine ce week-end de filtrage dont voici la boucle principale. Après avoir longuement optimisé le code général, j'ai eu un gain de 30%. Bien mais pas top. Après une écriture multi-thread de l'algo, j'ai eu un gain de 300% (quadcore).
 
Mais en regardant le code, je suis persuadé qu'il y a moyen d'écrire du code optimisé MMX,SSE ou autre pour faire une seule instruction pour les trois couleurs. Il est évident que dans ce cas, mon tableau gaussian_matrix serait un long long (ou autre?) qui contiendrait directement le multiplicateur écrit 3 fois, prêt à l'emploi.
 
J'ai cru comprendre qu'il existait des macro pour les compilateurs C, voir qu'ils savaient optimiser tout seul ce genre de chose (mais là, j'ai un doute). Qu'en est-il exactement?
 
J'utilise gcc (ubuntu 10.10)
 
Si quelqu'un peut me tuyauter là dessus, même un fucking manual, ou de l'inline asm
 
Merci  :jap:  
 

Code :
  1. while (sx) {
  2.  mat=gaussian_matrix[cx++];
  3.  tr+=wrkpix[pix]*mat;
  4.  tg+=wrkpix[pix+1]*mat;
  5.  tb+=wrkpix[pix+2]*mat;
  6.  pix+=3;
  7.  sx--;
  8. }


 
EDIT: Apparemment, y a l'instruction PMULLD qui pourrait m'aider mais c'est sur l'écriture gcc que je cale pour le moment...


Message édité par edwoud le 12-03-2012 à 13:57:33
Reply

Marsh Posté le 12-03-2012 à 08:28:11   

Reply

Marsh Posté le 12-03-2012 à 10:49:53    

Salut,  
 
Il vaudrait mieux travailler non pas sur 3 composantes par pixel mais 4 , car les instructions MMX/SSE font des opérations sur 64/128 bits, et la mémoire a besoin d'être alignée. Ca te boufferait un peu plus de mémoire ( 33% quand même ) mais tu pourrais utiliser certaines instructions. SSE te permettra juste de travailler sur plus de pixels à la fois que MMX ( 4 pour le SSE , et 2 pour le MMX ).

Message cité 1 fois
Message édité par xilebo le 12-03-2012 à 10:50:16
Reply

Marsh Posté le 12-03-2012 à 10:53:52    

xilebo a écrit :

Salut,  
 
Il vaudrait mieux travailler non pas sur 3 composantes par pixel mais 4 , car les instructions MMX/SSE font des opérations sur 64/128 bits, et la mémoire a besoin d'être alignée. Ca te boufferait un peu plus de mémoire ( 33% quand même ) mais tu pourrais utiliser certaines instructions. SSE te permettra juste de travailler sur plus de pixels à la fois que MMX ( 4 pour le SSE , et 2 pour le MMX ).


 
Oui, bien sûr! Je pourrais aussi compléter le sous-registre MMX ou SSE par un zéro avant ma boucle.
 
Mais comment faire?
 
Je suis sur la page http://gcc.gnu.org/onlinedocs/gcc/ [...] sions.html de la doc GNU
concernant les extensions vectorielles (afin d'utiliser mmx, sse, avx en fonction de l'archi)
mais c'est vraiment très léger sur la façon de l'utiliser. Je continue de chercher  :jap:


Message édité par edwoud le 12-03-2012 à 10:54:45
Reply

Marsh Posté le 12-03-2012 à 12:15:26    

Alors,
 
imagine tu veux appliquer un coefficient sur un pixel, voici comment on pourrait procéder :
 
syntaxe assembleur MSVC :  

Code :
  1. movdqa  xmm6, _coeff // tu charges au préalable ton coefficient sur 128 bits dans un registre. Par exemple 8 valeurs sur 16 bits contenant ton coeff 0x00C000C000C000C000C000C000C000C0
  2. boucle :
  3. movdqa  xmm0, [edi]     // tu charges dans ton registre SSE 4 pixels en même temps. On suppose que tu as chargé l'adresse de ton buffer dans edi.
  4. pxor          xmm4, xmm4    // mise à 0 du registre 4
  5. movdqa              xmm1,xmm0 // sauvegarde xmm0
  6. punpcklbw         xmm0, xmm4    // tu unpackes xmm0 combiné avec xmm4 Ceci pour les pixels 0 et 1 .Cela permet de mettre chaque composante sur 16 bits pour faire la multiplication. Voir : http://webster.cs.ucr.edu/AoA/Wind [...] Seta2.html
  7. pmullw  xmm0, xmm6  // tu multiplies chaque composante par le coeff ( pixel 0, 1 )
  8. punpckhbw         xmm1, xmm4  // unpack pixel 2 et 3 dans xmm1    
  9. pmullw  xmm1, xmm6 // tu multiplies chaque composante par le coeff ( pixel 2, 3 )
  10. psrlw          xmm0, 8      // division par 256 ( pixel 0,1 )
  11. psrlw          xmm1, 8     // division par 256 ( pixel 2,3 )
  12. packuswb         xmm0, xmm1 // tu repackes
  13. movdqa  [edi], xmm0  // tu sauvegardes
  14. add   edi, 16 // tu incrémentes de 16 octets
  15. dec   ecx // compteur de boucle
  16. jnz   boucle // tu boucles


 
syntaxe assembleur GCC  

Code :
  1. "movdqa  %3,   %%xmm6       \n\t"
  2. "movdqa  (%%edi), %%xmm0       \n\t"
  3. "pxor  %%xmm4,  %%xmm4       \n\t"
  4. "movdqa  %%xmm0,  %%xmm1       \n\t"
  5. "punpcklbw %%xmm4,  %%xmm0       \n\t"
  6. "pmullw  %%xmm6,  %%xmm0       \n\t"
  7. "punpckhbw %%xmm4,  %%xmm1       \n\t"
  8. "pmullw  %%xmm6,  %%xmm1       \n\t"
  9. "psrlw  $8,   %%xmm0       \n\t"
  10. "psrlw  $8,   %%xmm1       \n\t"
  11. "packuswb %%xmm1,  %%xmm0       \n\t"
  12. "movdqa  %%xmm0,  (%%edi)       \n\t"
  13. "addl  $16,  %%edi       \n\t"
  14.  //
  15.  // Loop again or break.
  16.  //
  17. "decl  %%ecx          \n\t"
  18. "jnz  d           \n\t"


 
On peut même travailler sur 8 pixels car il y a assez de registres, et il y a 2 pipelines qui peuvent travailler en parallèle ( à voir si ca marche vraiment ).
 
Tu peux t'inspirer de ça pour écrire ton code ( il te faudra utiliser également paddsw pour faire une addition ).
 
Attention, tout doit être aligné sur 16 octets, ton buffer, mais également la variable 128 bits qui contiendra ton coefficient mat ( sur 16 bits donc 8 fois ) et aussi tes 3 variables tr tg tb recevant le résultat ( sinon on peut avoir des crashs ).
 
PS : j'ai écrit ça en recopiant du code , je ne sais pas si il est exact , mais le principe est là.

Reply

Marsh Posté le 12-03-2012 à 13:25:08    

Merci! J'étudierai la possibilité de faire encore plus de multiplications à la fois avec des masques aussi.
 
De mon côté, j'ai trouvé ça qui fonctionne bien et reste indépendant de l'architecture
 

Code :
  1. typedef int v4si __attribute__ ((vector_size (4*sizeof(int))));
  2. union i4vector
  3. {
  4.         v4si v;
  5.         int i[4];
  6. };
  7. dans le code on déclare comme suit
  8. union i4vector a,b,c;
  9. on charge comme ça
  10. a.i[0]=pixels[pix++];
  11. a.i[1]=pixels[pix++];
  12. ...
  13. et pour multiplier, c'est tout con (pareil pour additions, soustractions, divisions)
  14. c.v=a.v*b.v;
  15. désassemblé comme suit ce qui semble optimal, 4 instructions pour charger, multiplier et décharger
  16.   4005fc:       66 0f 6f 4d d0          movdqa -0x30(%rbp),%xmm1
  17.   400601:       66 0f 6f 45 c0          movdqa -0x40(%rbp),%xmm0
  18.   400606:       66 0f 38 40 c1          pmulld %xmm1,%xmm0
  19.   40060b:       66 0f 7f 45 b0          movdqa %xmm0,-0x50(%rbp)
  20. printf("%d %d %d %d\n",c.i[0],c.i[1],c.i[2],c.i[3]);
  21. }


Message édité par edwoud le 24-03-2012 à 17:11:53
Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed