Портирование инструкций MMX / SSE в AltiVec - PullRequest
3 голосов
/ 04 декабря 2010

Позвольте мне предвосхитить это с ... У меня крайне ограниченный опыт работы с ASM и еще меньше с SIMD.

Но бывает, что у меня есть следующий оптимизированный код MMX / SSE, который я хотел бы перенести в инструкции AltiVec для использования на процессорах PPC / Cell.

Это, вероятно, большой вопрос ... Хотя это всего лишь несколько строк кода, у меня не было никаких проблем, пытаясь понять, что здесь происходит.

Оригинальная функция:

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

Какие-нибудь советы, как мне переписать это, чтобы использовать инструкции AltiVec?

Моя первая попытка (очень неправильная) выглядит примерно так ... Но это не совсем (или даже удаленно) правильно.

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

Ответы [ 2 ]

3 голосов
/ 04 декабря 2010

Вы не за горами - я исправил несколько мелких проблем, немного почистил код, добавил тестовый жгут, и теперь он работает нормально:

#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 голос
/ 04 декабря 2010

(Внимание: весь мой опыт работы с Altivec связан с работой на Xbox360 / PS3 - я не уверен, насколько они отличаются от других платформ Altivec).

Прежде всего, вы должны проверить выравнивание указателя. Ожидается, что большинство операций векторной загрузки (и сохранения) будут выполняться с 16-байтовых выровненных адресов. Если это не так, то все будет происходить без предупреждения, но вы не получите данные, которые ожидали.

Можно (но медленнее) выполнять невыровненные загрузки, но в основном вам нужно немного прочитать до и после ваших данных и объединить их. См. Страницу Apple's Altivec . Я также сделал это перед использованием lvlx и lvrx инструкций загрузки, а затем ИЛИ их вместе.


Далее, я не уверен, что ваши умножения и добавления одинаковы. Я никогда не использовал ни _mm_madd_pi16, ни vec_msum, поэтому я не уверен, что они эквивалентны. Вы должны пройти через отладчик и убедиться, что они дают одинаковый вывод для одних и тех же входных данных. Другое возможное отличие состоит в том, что они могут по-разному трактовать переполнение (например, модульное или насыщенное).


И последнее, но не менее важное: вы вычисляете 4 дюйма за раз вместо 2. Таким образом, ваш союз должен содержать 4 дюйма, а в конце вы должны сложить все 4 из них.

...