2016-10-21 2 views
4

Je suis en train de mettre à jour du code de SSE à AVX2. En général, je peux voir que les instructions de collecte sont très utiles et bénéfiques pour la performance. Cependant, j'ai rencontré un cas où les instructions de regroupement sont moins efficaces que la décomposition des opérations de regroupement en opérations plus simples.Pourquoi deux instructions de regroupement consécutives sont-elles moins performantes que les opérations élémentaires équivalentes?

Dans le code ci-dessous, j'ai un vecteur de int32 b, un vecteur de double xi et 4 indices int32 emballés dans un registre 128 bits bidx. Je dois rassembler d'abord du vecteur b, que du vecteur xi. -À-dire, dans le code pseudo, je dois faire:

__m128i i = b[idx]; 
__m256d x = xi[i]; 

Dans la fonction ci-dessous, je mets en œuvre de deux façons en utilisant un #ifdef: via recueillir des instructions, ce qui donne un débit de 290 Mitre/s et par des opérations élémentaires, donnant un débit de 325 Mitre/sec.

Quelqu'un peut-il expliquer ce qui se passe? Merci

inline void resolve(const __m256d& z, const __m128i& bidx, int32_t j 
        , const int32_t *b, const double *xi, int32_t* ri) 
{ 

    __m256d x; 
    __m128i i; 

#if 0 // this code uses two gather instructions in sequence 

    i = _mm_i32gather_epi32(b, bidx, 4)); // i = b[bidx] 
    x = _mm256_i32gather_pd(xi, i, 8);  // x = xi[i] 

#else // this code does not use gather instructions 

    union { 
      __m128i vec; 
      int32_t i32[4]; 
    } u; 
    x = _mm256_set_pd 
      (xi[(u.i32[3] = b[_mm_extract_epi32(bidx,3)])] 
      , xi[(u.i32[2] = b[_mm_extract_epi32(bidx,2)])] 
      , xi[(u.i32[1] = b[_mm_extract_epi32(bidx,1)])] 
      , xi[(u.i32[0] = b[_mm_cvtsi128_si32(bidx) ])] 
      ); 
    i = u.vec; 

#endif 

    // here we use x and i 
    __m256 ps256 = _mm256_castpd_ps(_mm256_cmp_pd(z, x, _CMP_LT_OS)); 
    __m128 lo128 = _mm256_castps256_ps128(ps256); 
    __m128 hi128 = _mm256_extractf128_ps(ps256, 1); 
    __m128 blend = _mm_shuffle_ps(lo128, hi128, 0 + (2<<2) + (0<<4) + (2<<6)); 
    __m128i lt = _mm_castps_si128(blend); // this is 0 or -1 
    i = _mm_add_epi32(i, lt); 
    _mm_storeu_si128(reinterpret_cast<__m128i*>(ri)+j, i); 
} 
+5

Les instructions de regroupement sont très lentes. Je ne sais pas quel processeur vous avez, mais sur Haswell, je n'ai jamais trouvé un seul cas où rassembler était en fait plus rapide que le code scalaire manuel. Les choses sont censées être meilleures avec Broadwell et Skylake, mais je n'ai pas testé depuis. – Mysticial

+0

Je suis en train de tester sur Haswell. Selon mes tests, quand j'ai une instruction de collecte autonome plutôt que deux consécutives, la performance s'améliore. Par exemple, _mm256_i32gather_ps est plus rapide que la version manuelle, utilisant à la fois extract (qui est le pire) ou utilisant des unions. – Fabio

+1

Le débit réciproque de gather n'est pas disponible dans le guide intrinsèque d'Intel, mais Agner Fog a mesuré '_mm_i32gather_epi32' à 9 horloges, et pas encore répertorié pour' _mm256_i32gather_pd'. Ma conjecture est, il pourrait prendre ~ 18 horloges pour faire 2 rassemblements consécutifs, et votre code ci-dessous n'est pas assez long pour cacher la latence. Vous pouvez '-S' avec votre compilateur et vérifier ce qui est généré pour une version différente. – kftse

Répondre

-1

Puisque votre fonction 'resolve' est marquée comme inline, je suppose qu'elle est appelée dans une boucle haute fréquence. Ensuite, vous pouvez également regarder les dépendances des paramètres d'entrée les uns des autres en dehors de la fonction 'resolve'. Le compilateur pourrait être en mesure d'optimiser le code en ligne mieux à travers les limites de la boucle lors de l'utilisation de la variante de code scalaire.