Échange d’octets et de demi-mots SSE

Je voudrais traduire ce code en utilisant SSE insortingnsics.

for (uint32_t i = 0; i > 16) & 0xFFFF) | (value << 16); } 

Quelqu’un sait-il qu’il est insortingnsèque d’effectuer l’échange de mots 16 bits?

pshufb (SSSE3) devrait être plus rapide que 2 quarts de travail et un OU. En outre, une légère modification du masque de lecture aléatoire permettrait une conversion finale, au lieu d’un simple échange de mots.

voler la structure de fonction de Paul R, remplaçant juste les éléments insortingnsèques du vecteur:

 void word_swapping_ssse3(uint32_t* dest, const uint32_t* src, size_t count) { size_t i; __m128i shufmask = _mm_set_epi8(13,12, 15,14, 9,8, 11,10, 5,4, 7,6, 1,0, 3,2); // _mm_set args go in big-endian order for some reason. for (i = 0; i + 4 <= count; i += 4) { __m128i s = _mm_loadu_si128((__m128i*)&src[i]); __m128i d = _mm_shuffle_epi8(s, shufmask); _mm_storeu_si128((__m128i*)&dest[i], d); } for ( ; i < count; ++i) // handle residual elements { uint32_t w = src[i]; w = (w >> 16) | (w << 16); dest[i] = w; } } 

pshufb peut avoir un opérande mémoire, mais ce doit être le masque de pshufb aléatoire, pas les données à mélanger. Donc, vous ne pouvez pas l'utiliser comme une charge mélangée. : /

gcc ne génère pas un bon code pour la boucle. La boucle principale est

 # src: r8. dest: rcx. count: rax. shufmask: xmm1 .L16: movq %r9, %rax .L3: # first-iteration entry point movdqu (%r8), %xmm0 leaq 4(%rax), %r9 addq $16, %r8 addq $16, %rcx pshufb %xmm1, %xmm0 movups %xmm0, -16(%rcx) cmpq %rdx, %r9 jbe .L16 

Avec toute cette surcharge de boucle et nécessitant une instruction de chargement et de stockage distincte, le débit ne sera que de 1 lecture aléatoire par 2 cycles. (8 oups, depuis cmp macro-fuse avec jbe ).

Une boucle plus rapide serait

  shl $2, %rax # uint count -> byte count # check for %rax less than 16 and skip the vector loop # cmp / jsomething add %rax, %r8 # set up pointers to the end of the array add %rax, %rcx neg %rax # and count upwards toward zero .loop: movdqu (%r8, %rax), %xmm0 pshufb %xmm1, %xmm0 movups %xmm0, (%rcx, %rax) # IDK why gcc chooses movups for stores. Shorter encoding? add $16, %rax jl .loop # ... # scalar cleanup 

movdqu charges movdqu peuvent micro-fusionner avec des modes d'adressage complexes, à la différence des opérations vectorielles ALU, de sorte que toutes ces instructions sont uniques, à l'exception du magasin, je crois.

Cela devrait fonctionner à un cycle par itération avec un peu de déroulage, puisque vous pouvez add micro-fusible avec jl . Donc, la boucle a 5 uops total. 3 d'entre eux sont des opérations de chargement / stockage, qui ont des ports dédiés. Les goulots d'étranglement sont les suivants: pshufb ne peut s'exécuter que sur un seul port d'exécution (Haswell (SnB / IvB peut pshufb sur les ports 1 et 5)). Un magasin par cycle (toutes les microarches). Et enfin, la limite de 4 UOP de domaine fusionné par horloge pour les processeurs Intel, qui devrait être accessible sauf les cache-misses sur Nehalem et les versions ultérieures (tampon de boucle uop).

Un déroulage ramènerait le nombre total d'uops de domaine fondu par 16B en dessous de 4. L'incrémentation de pointeurs, au lieu d'utiliser des modes d'adressage complexes, laisserait les magasins micro-fusionner. (Réduire la surcharge de la boucle est toujours une bonne chose: laisser le tampon de réordre se remplir lors des itérations futures signifie que le processeur a quelque chose à faire quand il rencontre un imprévu à la fin de la boucle et revient à un autre code.)

C’est à peu près ce que vous obtiendriez en déroulant les boucles insortingnsèques, comme Elalfer suggère à juste titre que ce serait une bonne idée. Avec gcc, essayez -funroll-loops si le code ne gonfle pas trop.

BTW, il va probablement être préférable d’échanger des octets lors du chargement ou du stockage, mélangé avec un autre code, plutôt que de convertir un tampon en une opération séparée.

Le code scalaire de votre question ne correspond pas vraiment à la permutation d’ octets (au moins dans le sens d’une conversion de finalité): il s’agit simplement de permuter les bits 16 bits haut et bas dans un mot de 32 bits. Si c’est ce que vous voulez, réutilisez simplement la solution à votre question précédente , avec les modifications appropriées:

 void byte_swapping(uint32_t* dest, const uint32_t* src, size_t count) { size_t i; for (i = 0; i + 4 <= count; i += 4) { __m128i s = _mm_loadu_si128((__m128i*)&src[i]); __m128i d = _mm_or_si128(_mm_slli_epi32(s, 16), _mm_srli_epi32(s, 16)); _mm_storeu_si128((__m128i*)&dest[i], d); } for ( ; i < count; ++i) // handle residual elements { uint32_t w = src[i]; w = (w >> 16) | (w << 16); dest[i] = w; } }