点产品/w霓虹灯本质
Dot Product /w Neon Intrinsics
我正试图使用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的一组示例(包括点产品实现)来说,这是非常好的。
- 霓虹灯中的_mm_hadd_ps相当于什么?
- 如何清除霓虹灯中除第一个非零车道之外的所有车道?
- 在手臂霓虹灯中有效地重新洗牌和组合 16 个 3 位数字
- 为什么用于乘法、加法的霓虹灯内联函数比运算符慢?
- 在手臂霓虹灯中有效地组合面膜
- 在手臂霓虹灯中有效地积累符号位
- C++17 支持日食霓虹灯
- 霓虹灯增加了运行时间
- ARM霓虹灯优化-消除多余的负载
- 特征3.3中的手臂霓虹灯改善
- 矩阵乘法优化(使用霓虹灯模拟效果与着色器)
- C /SSE代码的有效霓虹灯内在
- 是否有SSE2 _MM_UNPACKHI/LO_EPI32/64和_MM_SHUFFLE_EPI8/32的霓虹灯等效物
- 为什么在实践中向右移动在霓虹灯和SSE中向左移动(反之亦然)
- 将SSE转换为霓虹灯:如何打包然后提取32位结果
- 手臂霓虹灯转置 4x4 uint32
- 编译带有活动霓虹灯标志的代码几乎没有任何改进(甚至恶化)
- 点产品/w霓虹灯本质
- 解释ARM霓虹灯图像采样