最初for循环:
float SumSquareError_C(const float* src_a, const float* src_b, int count)
{
float sse = 0u;
int i;
for (i = 0; i < count; ++i) {
float diff = src_a[i] - src_b[i];
sse += (float)(diff * diff);
}
return sse;
}
优化1:
float SumSquareError_NEON1(const float* src_a, const float* src_b, int count)
{
float sse;
asm volatile (
// Clear q8, q9, q10, q11
"veor q8, q8, q8 \n"
"veor q9, q9, q9 \n"
"veor q10, q10, q10 \n"
"veor q11, q11, q11 \n"
"1: \n"
"vld1.32 {q0, q1}, [%[src_a]]! \n"
"vld1.32 {q2, q3}, [%[src_a]]! \n"
"vld1.32 {q12, q13}, [%[src_b]]! \n"
"vld1.32 {q14, q15}, [%[src_b]]! \n"
"subs %[count], %[count], #16 \n"
// q0, q1, q2, q3 are the destination of vsub.
// they are also the source of vmla.
"vsub.f32 q0, q0, q12 \n"
"vmla.f32 q8, q0, q0 \n"
"vsub.f32 q1, q1, q13 \n"
"vmla.f32 q9, q1, q1 \n"
"vsub.f32 q2, q2, q14 \n"
"vmla.f32 q10, q2, q2 \n"
"vsub.f32 q3, q3, q15 \n"
"vmla.f32 q11, q3, q3 \n"
"bgt 1b \n"
"vadd.f32 q8, q8, q9 \n"
"vadd.f32 q10, q10, q11 \n"
"vadd.f32 q11, q8, q10 \n"
"vpadd.f32 d2, d22, d23 \n"
"vpadd.f32 d0, d2, d2 \n"
"vmov.32 %3, d0[0] \n"
: "+r"(src_a),
"+r"(src_b),
"+r"(count),
"=r"(sse)
:
: "memory", "cc", "q0", "q1", "q2", "q3", "q8", "q9", "q10", "q11",
"q12", "q13","q14", "q15");
return sse;
}
去除数据依赖的改进:
float SumSquareError_NEON2(const float* src_a, const float* src_b, int count)
{
float sse;
asm volatile (
// Clear q8, q9, q10, q11
"veor q8, q8, q8 \n"
"veor q9, q9, q9 \n"
"veor q10, q10, q10 \n"
"veor q11, q11, q11 \n"
"1: \n"
"vld1.32 {q0, q1}, [%[src_a]]! \n"
"vld1.32 {q2, q3}, [%[src_a]]! \n"
"vld1.32 {q12, q13}, [%[src_b]]! \n"
"vld1.32 {q14, q15}, [%[src_b]]! \n"
"subs %[count], %[count], #16 \n"
"vsub.f32 q0, q0, q12 \n"
"vsub.f32 q1, q1, q13 \n"
"vsub.f32 q2, q2, q14 \n"
"vsub.f32 q3, q3, q15 \n"
"vmla.f32 q8, q0, q0 \n"
"vmla.f32 q9, q1, q1 \n"
"vmla.f32 q10, q2, q2 \n"
"vmla.f32 q11, q3, q3 \n"
"bgt 1b \n"
"vadd.f32 q8, q8, q9 \n"
"vadd.f32 q10, q10, q11 \n"
"vadd.f32 q11, q8, q10 \n"
"vpadd.f32 d2, d22, d23 \n"
"vpadd.f32 d0, d2, d2 \n"
"vmov.32 %3, d0[0] \n"
: "+r"(src_a),
"+r"(src_b),
"+r"(count),
"=r"(sse)
:
: "memory", "cc", "q0", "q1", "q2", "q3", "q8", "q9", "q10", "q11",
"q12", "q13","q14", "q15");
return sse;
}
在NEON实现1中,目标寄存器被立即用作源寄存器;在NEON实现2中,指令被重新调度并尽可能多地赋予延迟。测试结果表明,实现2比实现1快30%。因此,减少数据依赖性可以显著提高性能。一个好消息是编译器可以自动微调NEON内部函数,以避免数据依赖,这是一个很大的优势。