Effacer les octets supérieurs de __m128i

Comment effacer les 16 - i octets supérieurs d’un __m128i ?

J’ai essayé ça cela fonctionne, mais je me demande s’il existe un meilleur moyen (plus court, plus rapide):

 int i = ... // 0 < i  14) ? -1 : 0, (i > 13) ? -1 : 0, (i > 12) ? -1 : 0, (i > 11) ? -1 : 0, (i > 10) ? -1 : 0, (i > 9) ? -1 : 0, (i > 8) ? -1 : 0, (i > 7) ? -1 : 0, (i > 6) ? -1 : 0, (i > 5) ? -1 : 0, (i > 4) ? -1 : 0, (i > 3) ? -1 : 0, (i > 2) ? -1 : 0, (i > 1) ? -1 : 0, -1); x = _mm_and_si128(x, mask); 

J’ai essayé différentes manières de mettre en œuvre ceci et les ai comparés avec deux compilateurs différents sur les premiers Core i7 @ 2,67 GHz et les récents Haswell @ 3,6 GHz:

 // // mask_shift_0 // // use PSHUFB (note: SSSE3 required) // inline __m128i mask_shift_0(uint32_t n) { const __m128i vmask = _mm_set1_epi8(255); const __m128i vperm = _mm_set_epi8(112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127); __m128i vp = _mm_add_epi8(vperm, _mm_set1_epi8(n)); return _mm_shuffle_epi8(vmask, vp); } // // mask_shift_1 // // use 16 element LUT // inline __m128i mask_shift_1(uint32_t n) { static const int8_t mask_lut[16][16] __atsortingbute__ ((aligned(16))) = { { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1 } }; return _mm_load_si128((__m128i *)&mask_lut[n]); } // // mask_shift_2 // // use misaligned load from 2 vector LUT // inline __m128i mask_shift_2(uint32_t n) { static const int8_t mask_lut[32] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; return _mm_loadu_si128((__m128i *)(mask_lut + 16 - n)); } // // mask_shift_3 // // use compare and single vector LUT // inline __m128i mask_shift_3(uint32_t n) { const __m128i vm = _mm_setr_epi8(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); __m128i vn = _mm_set1_epi8(n); return _mm_cmpgt_epi8(vm, vn); } // // mask_shift_4 // // use jump table and immediate shifts // inline __m128i mask_shift_4(uint32_t n) { const __m128i vmask = _mm_set1_epi8(-1); switch (n) { case 0: return vmask; case 1: return _mm_slli_si128(vmask, 1); case 2: return _mm_slli_si128(vmask, 2); case 3: return _mm_slli_si128(vmask, 3); case 4: return _mm_slli_si128(vmask, 4); case 5: return _mm_slli_si128(vmask, 5); case 6: return _mm_slli_si128(vmask, 6); case 7: return _mm_slli_si128(vmask, 7); case 8: return _mm_slli_si128(vmask, 8); case 9: return _mm_slli_si128(vmask, 9); case 10: return _mm_slli_si128(vmask, 10); case 11: return _mm_slli_si128(vmask, 11); case 12: return _mm_slli_si128(vmask, 12); case 13: return _mm_slli_si128(vmask, 13); case 14: return _mm_slli_si128(vmask, 14); case 15: return _mm_slli_si128(vmask, 15); } } // // lsb_mask_0 // // Consortingbuted by by @Leeor/@dtb // // uses _mm_set_epi64x // inline __m128i lsb_mask_0(int n) { if (n >= 8) return _mm_set_epi64x(~(-1LL << (n - 8) * 8), -1); else return _mm_set_epi64x(0, ~(-1LL << (n - 0) * 8)); } // // lsb_mask_1 // // Contributed by by @Leeor/@dtb // // same as lsb_mask_0 but uses conditional operator instead of if/else // inline __m128i lsb_mask_1(int n) { return _mm_set_epi64x(n >= 8 ? ~(-1LL << (n - 8) * 8) : 0, n >= 8 ? -1 : ~(-1LL << (n - 0) * 8)); } 

Les résultats étaient intéressants:

Core i7 à 2,67 GHz, Apple LLVM gcc 4.2.1 (gcc -O3)

 mask_shift_0: 2.23377 ns mask_shift_1: 2.14724 ns mask_shift_2: 2.14270 ns mask_shift_3: 2.15063 ns mask_shift_4: 2.98304 ns lsb_mask_0: 2.15782 ns lsb_mask_1: 2.96628 ns 

Core i7 @ 2,67 GHz, Apple Clang 4.2 (Clang -Os)

 mask_shift_0: 1.35014 ns mask_shift_1: 1.12789 ns mask_shift_2: 1.04329 ns mask_shift_3: 1.09258 ns mask_shift_4: 2.01478 ns lsb_mask_0: 1.70573 ns lsb_mask_1: 1.84337 ns 

Haswell E3-1285 à 3,6 GHz, gcc 4.7.2 (gcc -O2)

 mask_shift_0: 0.851416 ns mask_shift_1: 0.575245 ns mask_shift_2: 0.577746 ns mask_shift_3: 0.850086 ns mask_shift_4: 1.398270 ns lsb_mask_0: 1.359660 ns lsb_mask_1: 1.709720 ns 

Donc mask_shift_4 (switch / case) semble être la méthode la plus lente dans tous les cas, alors que les autres sont assez similaires. Les méthodes basées sur la LUT semblent être systématiquement les plus rapides.

NB: je reçois des nombres clang -O3 rapides avec clang -O3 et gcc -O3 (gcc 4.7.2 uniquement) - J'ai besoin de regarder l'assembly généré pour ces cas pour voir ce que fait le compilateur et s'assurer qu'il ne le fait pas. tout ce qui est "intelligent", tel que l'optimisation d'une partie du faisceau de test de synchronisation.

Si quelqu'un d'autre a d'autres idées à ce sujet ou a une autre implémentation mask_shift qu'il aimerait essayer, je serais heureux de l'append à la suite de tests et de mettre à jour les résultats.

S’il s’agissait de valeurs 64 bits normales, j’utiliserais quelque chose comme:

  mask = (1 << (i * 8)) - 1; 

Mais attention, en généralisant ceci à 128, les opérateurs de quart internes ne travaillent pas nécessairement à ces plages.

Pour 128b, vous pouvez simplement construire un masque supérieur et inférieur, par exemple -

  __m128i mask = _mm_set_epi64x( i > 7 ? 0xffffffff : (1 << ((i) * 8)) - 1 i > 7 ? (1 << ((i-8) * 8)) - 1 : 0 ); 

(en supposant que je n'ai pas échangé l'ordre, vérifiez-le sur celui-ci, je ne suis pas très familiarisé avec ces éléments insortingnsèques). Vous pouvez également le faire sur un tableau uint64 sur 2 larges et charger le masque de 128b directement à partir de la mémoire en utilisant l'adresse .

Cependant, ces deux méthodes ne semblent pas naturelles comme l'originale, elles ne font que prolonger les éléments de 1 à 8 octets, mais sont encore partielles. Il serait bien préférable d’effectuer un décalage approprié avec une seule variable de 128b.

Je viens de tomber sur ce sujet concernant les changements de 128b -

Recherche d'une opération de décalage sse 128 bits pour une valeur de décalage non immédiate

on dirait que c'est possible mais je ne l'ai jamais utilisé. Vous pouvez essayer la ligne ci-dessus avec l’approche SSE appropriée à partir de là. Je donnerais un coup à celui-ci -

  mask = _mm_slli_si128(1, i); //emminsortingn.h shows the second argument is in bytes already 

Et puis soustrayez-en un en utilisant votre méthode préférée (je serais surpris que ce type prenne en charge un ancien opérateur standard)