Question

Permettez-moi de cette préface avec .. J'ai une expérience très limitée avec ASM, et encore moins avec SIMD.

Mais il arrive que j'ai le code optimisé MMX / SSE suivant, que je voudrais au port dans les instructions AltiVec pour une utilisation sur les processeurs PPC / cellulaires.

Ceci est probablement une grande demande .. Bien que ce soit seulement quelques lignes de code, j'ai eu des difficultés sans fin pour essayer de trouver ce qui se passe ici.

La fonction d'origine:

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 à quelque chose comme ça .. Mais il est pas tout à fait (ou même à distance) correcte.

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;
}
Était-ce utile?

La solution

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;
}

Autres conseils

. (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 devez vérifier l'alignement de votre pointeur. La plupart des charges vectorielles (et magasins) opérations devraient être alignées à partir d'adresses de 16 octets. Si elles ne sont pas, les choses portent habituellement sans avertissement, mais vous ne serez pas obtenir les données que vous attendiez.

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


Ensuite, je ne suis pas sûr que vos multiplications et ajoute sont les mêmes. Je ne l'ai jamais utilisé soit _mm_madd_pi16 ou vec_msum, donc je ne suis-ils pas positif équivalent êtes la. Vous devriez y dans 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 différemment trop-plein (par exemple modulaire vs. saturée).


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.

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