点产品/w霓虹灯本质

Dot Product /w Neon Intrinsics

本文关键字:霓虹灯      更新时间:2023-10-16

我正试图使用Neon内部函数为ARM A8处理器编写一个优化的点积,但遇到了一点小麻烦。首先,有没有任何库已经实现了这一点?我的代码似乎可以工作,但在运行时会导致一些安静的故障——我的最佳猜测是,与未优化的代码相比,精度略有下降。有没有更好的方法来完成我想要做的事情?如果有任何帮助或建议,我将不胜感激。提前谢谢。

这个特殊的点积是一个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++;
    }

如果您使用的是iOS,请在Accelerate框架中使用vDSP_zrdotpr。(vDSP_zrdotpr返回实数向量与复数向量的点积。还有其他变体,例如实数对实数或复数对复数。)

当然,这是一种精度的损失;您的未优化代码累积双精度和,而NEON代码累积单精度和。

即使没有精度变化,结果也会有所不同,因为以不同的顺序进行浮点运算会产生不同的舍入误差。(整数也是如此;如果你计算7/3*5,你会得到10,但5*7/3是11。)

有一些算法可以减少浮点运算的误差。然而,对于高性能的点产品,您通常会拘泥于所得到的。

一种选择是使用双精度近地天体指令进行运算。当然,这不会像单精度近地天体那样快,但它会比标量(非近地天体)代码更快。

这是我所做的一些事情,目前正在商业产品中。希望它能有所帮助。唯一的要求是两个被乘数(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          nt" // zero out q8 register
              "1:                         nt"
              "subs %3, %3, #4            nt" // we load 4 floats into q0, and q2 register
              "vld1.f32 {d0,d1}, [%1]!    nt" // loads q0, update pointer *a
              "vld1.f32 {d4,d5}, [%2]!    nt" // loads q2, update pointer *b
              "vmla.f32 q8, q0, q2        nt" // store four partial sums in q8
              "bgt 1b                     nt"   // loops to label 1 until n==0
              "vpadd.f32 d0, d16, d17     nt"   // pairwise add 4 partial sums in q8, store in d0
              "vadd.f32 %0, s0, s1        nt"   // 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;
}  

至于其他实现,还有来自ARM的NEON OpenMAX DL实现。链接到来自http://www.arm.com/community/multimedia/standards-apis.php.

下载需要注册,格式是RVCT汇编程序,但对于如何使用NEON的一组示例(包括点产品实现)来说,这是非常好的。