Question

J'ai un code dans une boucle

for(int i = 0; i < n; i++)
{
  u[i] = c * u[i] + s * b[i];
}

Ainsi, u et b sont des vecteurs de même longueur, et c et d sont des scalaires. Est-ce code un bon candidat pour la vectorisation pour une utilisation avec SSE afin d'obtenir un gain de vitesse?

UPDATE

Je l'ai appris vectorisation (tour à tour sur c'est pas si difficile si vous utilisez intrinsics) et mis en œuvre ma boucle SSE. Cependant, lors du réglage du drapeau SSE2 dans le compilateur du VC, je reçois les mêmes performances qu'avec mon propre code SSE. Le compilateur Intel d'autre part était beaucoup plus rapide que mon code SSE ou le compilateur de la VC.

Voici le code que j'ai écrit pour référence

double *u = (double*) _aligned_malloc(n * sizeof(double), 16);
for(int i = 0; i < n; i++)
{
   u[i] = 0;
}

int j = 0;
__m128d *uSSE = (__m128d*) u;
__m128d cStore = _mm_set1_pd(c);
__m128d sStore = _mm_set1_pd(s);
for (j = 0; j <= i - 2; j+=2)
{
  __m128d uStore = _mm_set_pd(u[j+1], u[j]);

  __m128d cu = _mm_mul_pd(cStore, uStore);
  __m128d so = _mm_mul_pd(sStore, omegaStore);

  uSSE[j/2] = _mm_add_pd(cu, so);
}
for(; j <= i; ++j)
{
  u[j] = c * u[j] + s * omegaCache[j];
}
Était-ce utile?

La solution

Oui, c'est un excellent candidat pour la vectorisation. Mais, avant de le faire, Assurez-vous que vous avez votre code PROFILES pour être sûr que ce soit réellement une valeur d'optimisation. Cela dit, la vectorisation irait quelque chose comme ceci:

int i;
for(i = 0; i < n - 3; i += 4)
{
  load elements u[i,i+1,i+2,i+3]
  load elements b[i,i+1,i+2,i+3]
  vector multiply u * c
  vector multiply s * b
  add partial results
  store back to u[i,i+1,i+2,i+3]
}

// Finish up the uneven edge cases (or skip if you know n is a multiple of 4)
for( ; i < n; i++)
  u[i] = c * u[i] + s * b[i];

Pour encore plus de performance, vous pouvez envisager préchargement d'autres éléments du tableau, et / ou la boucle et dérouler en utilisant le logiciel pour entrelacer le calcul dans une boucle avec la mémoire accède à partir d'une autre itération.

Autres conseils

_mm_set_pd non vectorisé. Pris à la lettre, il lit les deux doubles en utilisant des opérations scalaires, combine ensuite les deux doubles scalaires et les copier dans le registre SSE. Utilisez _mm_load_pd à la place.

Oui, c'est un excellent candidat pour vectorizaton, en supposant qu'il n'y a pas de chevauchement de la matrice U et B. Mais le code est lié par un accès de mémoire (chargement / stockage). Vectorisation permet de réduire les cycles par boucle, mais les instructions calera en raison de cache-miss sur la matrice U et B. Le processeur Intel C / C ++ compilateur génère le code suivant de drapeaux par défaut pour le processeur Xeon x5500. Le compilateur se déroule la boucle en huit et emploie des instructions ADD SIMD (de addpd) et MULTIPLY (mulpd) en utilisant XMM [0-16] registres SIMD. Dans chaque cycle, le processeur peut émettre 2 instructions SIMD 4 voies donnant ILP scalaire, supposant que vous avez les données prêtes dans les registres.

Ici, U, B, C et S sont double précision (8 octets).

    ..B1.14:                        # Preds ..B1.12 ..B1.10
    movaps    %xmm1, %xmm3                                  #5.1
    unpcklpd  %xmm3, %xmm3                                  #5.1
    movaps    %xmm0, %xmm2                                  #6.12
    unpcklpd  %xmm2, %xmm2                                  #6.12
      # LOE rax rcx rbx rbp rsi rdi r8 r12 r13 r14 r15 xmm0 xmm1 xmm2 xmm3
    ..B1.15:     # Preds ..B1.15 ..B1.14
    movsd     (%rsi,%rcx,8), %xmm4                          #6.21
    movhpd    8(%rsi,%rcx,8), %xmm4                         #6.21
    mulpd     %xmm2, %xmm4                                  #6.21
    movaps    (%rdi,%rcx,8), %xmm5                          #6.12
    mulpd     %xmm3, %xmm5                                  #6.12
    addpd     %xmm4, %xmm5                                  #6.21
    movaps    16(%rdi,%rcx,8), %xmm7                        #6.12
    movaps    32(%rdi,%rcx,8), %xmm9                        #6.12
    movaps    48(%rdi,%rcx,8), %xmm11                       #6.12
    movaps    %xmm5, (%rdi,%rcx,8)                          #6.3
    mulpd     %xmm3, %xmm7                                  #6.12
    mulpd     %xmm3, %xmm9                                  #6.12
    mulpd     %xmm3, %xmm11                                 #6.12
    movsd     16(%rsi,%rcx,8), %xmm6                        #6.21
    movhpd    24(%rsi,%rcx,8), %xmm6                        #6.21
    mulpd     %xmm2, %xmm6                                  #6.21
    addpd     %xmm6, %xmm7                                  #6.21
    movaps    %xmm7, 16(%rdi,%rcx,8)                        #6.3
    movsd     32(%rsi,%rcx,8), %xmm8                        #6.21
    movhpd    40(%rsi,%rcx,8), %xmm8                        #6.21
    mulpd     %xmm2, %xmm8                                  #6.21
    addpd     %xmm8, %xmm9                                  #6.21
    movaps    %xmm9, 32(%rdi,%rcx,8)                        #6.3
    movsd     48(%rsi,%rcx,8), %xmm10                       #6.21
    movhpd    56(%rsi,%rcx,8), %xmm10                       #6.21
    mulpd     %xmm2, %xmm10                                 #6.21
    addpd     %xmm10, %xmm11                                #6.21
    movaps    %xmm11, 48(%rdi,%rcx,8)                       #6.3
    addq      $8, %rcx                                      #5.1
    cmpq      %r8, %rcx                                     #5.1
    jl        ..B1.15       # Prob 99%                      #5.1

il dépend de la façon dont vous avez placé u et b en mémoire. si les deux blocs de mémoire sont loin les uns des autres, SSE ne serait pas coup de fouet dans ce scénario.

il est suggéré que le tableau u et b sont AOE (tableau de la structure) au lieu de SOA (structure du tableau), parce que vous pouvez charger tous les deux dans le registre dans l'instruction unique.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top