2010-12-04 4 views
3

Laissez-moi préface avec .. J'ai une expérience extrêmement limitée avec ASM, et encore moins avec SIMD.Porting instructions MMX/SSE à AltiVec

Mais il se trouve que j'ai le code optimisé suivant MMX/SSE, que je voudrais transférer vers les instructions AltiVec pour une utilisation sur les processeurs PPC/Cell.

Ceci est probablement une grande question. Même si ce n'est que quelques lignes de code, je n'ai eu aucun mal à essayer de comprendre ce qui se passe ici.

La fonction originale:

static inline int convolve(const short *a, const short *b, int n) 
{ 
    int out = 0; 
    union { 
     __m64 m64; 
     int i32[2]; 
    } tmp; 
    tmp.i32[0] = 0; 
    tmp.i32[1] = 0; 
    while (n >= 4) { 
     tmp.m64 = _mm_add_pi32(tmp.m64, 
           _mm_madd_pi16(*((__m64 *)a), 
              *((__m64 *)b))); 
     a += 4; 
     b += 4; 
     n -= 4; 
    } 
    out = tmp.i32[0] + tmp.i32[1]; 
    _mm_empty(); 

    while (n --) 
     out += (*(a++)) * (*(b++)); 
    return out; 
} 

Des conseils sur la façon dont je pourrais réécrire cette option pour utiliser les instructions AltiVec?

Ma première tentative (une très mauvaise tentative) ressemble à ceci ... Mais ce n'est pas entièrement (ou même à distance) correct.

static inline int convolve_altivec(const short *a, const short *b, int n) 
{ 
    int out = 0; 
    union { 
     vector unsigned int m128; 
     int i64[2]; 
    } tmp; 

    vector unsigned int zero = {0, 0, 0, 0}; 

    tmp.i64[0] = 0; 
    tmp.i64[1] = 0; 
    while (n >= 8) { 
     tmp.m128 = vec_add(tmp.m128, 
           vec_msum(*((vector unsigned short *)a), 
              *((vector unsigned short *)b), zero)); 

     a += 8; 
     b += 8; 
     n -= 8; 
    } 
    out = tmp.i64[0] + tmp.i64[1]; 
#endif 
    while (n --) 
     out += (*(a++)) * (*(b++)); 
    return out; 
} 

Répondre

3

Vous n'êtes pas loin - je fixe quelques problèmes mineurs, nettoyé un peu le code, a ajouté un harnais de test, et il semble fonctionner OK maintenant:

#include <assert.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <altivec.h> 

static int convolve_ref(const short *a, const short *b, int n) 
{ 
    int out = 0; 
    int i; 

    for (i = 0; i < n; ++i) 
    { 
     out += a[i] * b[i]; 
    } 

    return out; 
} 

static inline int convolve_altivec(const short *a, const short *b, int n) 
{ 
    int out = 0; 
    union { 
     vector signed int m128; 
     int i32[4]; 
    } tmp; 

    const vector signed int zero = {0, 0, 0, 0}; 

    assert(((unsigned long)a & 15) == 0); 
    assert(((unsigned long)b & 15) == 0); 

    tmp.m128 = zero; 

    while (n >= 8) 
    { 
     tmp.m128 = vec_msum(*((vector signed short *)a), 
          *((vector signed short *)b), tmp.m128); 

     a += 8; 
     b += 8; 
     n -= 8; 
    } 

    out = tmp.i32[0] + tmp.i32[1] + tmp.i32[2] + tmp.i32[3]; 

    while (n --) 
     out += (*(a++)) * (*(b++)); 

    return out; 
} 

int main(void) 
{ 
    const int n = 100; 

    vector signed short _a[n/8 + 1]; 
    vector signed short _b[n/8 + 1]; 

    short *a = (short *)_a; 
    short *b = (short *)_b; 

    int sum_ref, sum_test; 

    int i; 

    for (i = 0; i < n; ++i) 
    { 
     a[i] = rand(); 
     b[i] = rand(); 
    } 

    sum_ref = convolve_ref(a, b, n); 
    sum_test = convolve_altivec(a, b, n); 

    printf("sum_ref = %d\n", sum_ref); 
    printf("sum_test = %d\n", sum_test); 

    printf("%s\n", sum_ref == sum_test ? "PASS" : "FAIL"); 

    return 0; 
} 
+1

brillant. Merci Paul. J'ai dû modifier le type de vecteur du tableau 'zéro' pour qu'il soit de type int signé (pour correspondre à celui de la variable m128) mais sinon cela a fonctionné un traitement absolu (et flamboie en termes de performance). Cela me donne envie d'en savoir plus sur les extensions SIMD. –

+0

@Tim Kane: super - content que cela fonctionne pour vous. Bien repéré sur le vecteur zéro - fixé maintenant. AltiVec est vraiment cool, mais malheureusement, il est sur le point de sortir maintenant. Il y a cependant des choses passionnantes sur SIMD à l'horizon: l'AVX d'Intel et le SSE5 d'AMD par exemple. –

1

(Attention: toute mon expérience Altivec vient de travailler sur Xbox360/PS3 - Je ne sais pas comment ils sont différents des autres plates-formes Altivec).

Tout d'abord, vous devriez vérifier l'alignement de votre pointeur. La plupart des opérations de chargement de vecteur (et de stockage) doivent provenir d'adresses alignées sur 16 octets. Si ce n'est pas le cas, les choses continueront normalement sans avertissement, mais vous n'obtiendrez pas les données que vous attendiez.

Il est possible (mais plus lent) de faire des charges non alignées, mais vous devez essentiellement lire un peu avant et après vos données et les combiner. Voir Apple's Altivec page. Je l'ai également fait avant d'utiliser les instructions de chargement lvlx et lvrx, puis de les combiner ensemble.


Ensuite, je ne suis pas sûr que vos multiplications et vos ajouts soient les mêmes. Je n'ai jamais utilisé _mm_madd_pi16 ou vec_msum, donc je ne suis pas sûr qu'ils soient équivalents. Vous devez parcourir un débogueur et assurez-vous qu'ils vous donnent la même sortie pour les mêmes données d'entrée. Une autre différence possible est qu'ils peuvent traiter un débordement différemment (par exemple, modulaire ou saturé).


Last but not least, vous calculer 4 ints à la fois au lieu de 2. Ainsi, votre syndicat devrait contenir 4 ints, et vous devriez résumer les 4 d'entre eux à la fin.