Скалярный продукт / w Neon Intrinsics

Я пытаюсь написать оптимизированный точечный продукт для процессора ARM A8, используя встроенные функции Neon, но у меня возникли небольшие проблемы. Прежде всего, есть ли какая-нибудь библиотека, которая уже это реализует? Мой код, кажется, работает, но вызывает некоторые тихие сбои во время выполнения - я думаю, это связано с небольшой потерей точности по сравнению с неоптимизированным кодом. Есть ли лучший способ выполнить то, что я пытаюсь сделать? Буду очень признателен за любую помощь или предложения. Заранее спасибо.

Этот конкретный скалярный продукт представляет собой 32-битный комплекс с плавающей запятой * 32-битный комплекс с плавающей запятой.

Вот неоптимизированный код:

    double sum_re = 0.0;
    double sum_im = 0.0;
    for(int i=0; i<len; i++, src1++, src2++)
    {
            sum_re += *src1 * src2->re;
            sum_im += *src1 * src2->im;
    }

Вот моя оптимизированная версия:

    float sum_re = 0.0;
    float sum_im = 0.0;

    float to_sum_re[4] = {0,0,0,0};
    float to_sum_im[4] = {0,0,0,0};

    float32x4_t tmp_sum_re, tmp_sum_im, source1;
    float32x4x2_t source2;
    tmp_sum_re = vld1q_f32(to_sum_re);
    tmp_sum_im = vld1q_f32(to_sum_im);

    int i = 0;

    while (i < (len & ~3)) {
            source1 = vld1q_f32(&src1[i]);
            source2 = vld2q_f32((const float32_t*)&src2[i]);

            tmp_sum_re = vmlaq_f32(tmp_sum_re, source1, source2.val[0]);
            tmp_sum_im = vmlaq_f32(tmp_sum_im, source1, source2.val[1]);

            i += 4;
    }
    if (len & ~3) {
            vst1q_f32(to_sum_re, tmp_sum_re);
            vst1q_f32(to_sum_im, tmp_sum_im);

            sum_re += to_sum_re[0] + to_sum_re[1] + to_sum_re[2] + to_sum_re[3];
            sum_im += to_sum_im[0] + to_sum_im[1] + to_sum_im[2] + to_sum_im[3];
    }

    while (i < len)
    {
            sum_re += src1[i] * src2[i].re;
            sum_im += src1[i] * src2[i].im;
            i++;
    }

person Tan Man    schedule 11.07.2012    source источник
comment
Вы писали результаты и проверяли, отличаются ли они и насколько?   -  person Dani    schedule 11.07.2012
comment
Это может быть нерепрезентативный пример, но при первом вызове функции: - - - - С НЕОНОМ: {re = 54.041008, im = 29.197485} БЕЗ НЕОНА: {re = 54.0410004, im = 29.1974678}   -  person Tan Man    schedule 11.07.2012
comment
Ettus e110 (похож на бигльборд) под управлением Angstrom   -  person Tan Man    schedule 11.07.2012


Ответы (3)


Если вы используете iOS, используйте vDSP_zrdotpr в структуре Accelerate. (vDSP_zrdotpr возвращает скалярное произведение действительного вектора на комплексный вектор. Существуют и другие варианты, например, действительное-действительное или комплексное-сложное.)

Конечно, есть потеря точности; ваш неоптимизированный код накапливает суммы с двойной точностью, тогда как код NEON накапливает суммы с одинарной точностью.

Даже без изменения точности можно было бы ожидать, что результаты будут отличаться, поскольку выполнение операций с плавающей запятой в разных порядках приводит к разным ошибкам округления. (Это верно и для целых чисел: если вы посчитаете 7/3*5, вы получите 10, но 5*7/3 равно 11.)

Существуют алгоритмы для выполнения арифметических операций с плавающей запятой с уменьшенной ошибкой. Однако для создания высокопроизводительного скалярного произведения вы, как правило, зацикливаетесь на том, что получаете.

Одним из вариантов было бы выполнить арифметику с инструкциями NEON двойной точности. Конечно, это будет не так быстро, как NEON с одинарной точностью, но это будет быстрее, чем скалярный (не NEON) код.

person Eric Postpischil    schedule 11.07.2012
comment
Два примечания. Несмотря на то, что вы накапливаете суммы с двойной точностью в скалярном коде, умножение выполняется с одинарной точностью, поэтому окончательные ошибки типичны для этого, а не для того, что могло бы быть результатом двойной точности. И хотя vDSP_zrdotpr может умножаться на чередующийся комплексный вектор (с шагом два), я подозреваю, что он не оптимизирован для этого случая. Вы можете подать отчет об ошибке, запросив его на bugreport.apple.com. - person Eric Postpischil; 11.07.2012

Вот что я сделал и в настоящее время в коммерческом продукте. Надеюсь, это поможет. Единственное требование состоит в том, что два множимых (src1, srcs->re) должны быть кратны четырем.

float dotProduct4 (const float *a, const float *b, int n) {
float net1D=0.0f;
assert(n%4==0);     // required floats 'a' & 'b' to be multiple of 4
#ifdef __ARM_NEON__
asm volatile (
              "vmov.f32 q8, #0.0          \n\t" // zero out q8 register
              "1:                         \n\t"
              "subs %3, %3, #4            \n\t" // we load 4 floats into q0, and q2 register
              "vld1.f32 {d0,d1}, [%1]!    \n\t" // loads q0, update pointer *a
              "vld1.f32 {d4,d5}, [%2]!    \n\t" // loads q2, update pointer *b
              "vmla.f32 q8, q0, q2        \n\t" // store four partial sums in q8
              "bgt 1b                     \n\t"   // loops to label 1 until n==0
              "vpadd.f32 d0, d16, d17     \n\t"   // pairwise add 4 partial sums in q8, store in d0
              "vadd.f32 %0, s0, s1        \n\t"   // add 2 partial sums in d0, store result in return variable net1D
              : "=w"(net1D)                 // output
              : "r"(a), "r"(b), "r"(n)      // input
              : "q0", "q2", "q8");          // clobber list
#else
for (int k=0; k < n; k++) {
    net1D += a[k] * b[k];
}
#endif
return net1D;
}  
person Loozie    schedule 03.07.2013

Что касается других реализаций, то есть реализация NEON OpenMAX DL от ARM. Связано с http://www.arm.com/community/multimedia/standards-apis.php.

Для скачивания требуется регистрация, а формат - ассемблер RVCT, но для просмотра набора примеров использования NEON (включая реализацию точечного продукта) он довольно хорош.

person unixsmurf    schedule 14.07.2012