NEON ASM代码的运行速度比C代码慢得多?

| 我正在尝试使用NEON在iPhone ARM上针对特定问题实施高斯-牛顿优化。下面的第一个函数是我原来的C函数。第二个是我编写的NEON asm代码。我每运行100,000次,NEON版本的时间是C版本的7-8倍。我认为加载(vld1.32)是大多数时间的工作。我通过删除一些说明进行了实验。 有人对此问题有见识吗?谢谢!
template<class T>
inline void GaussNewtonOperationJtr8x8(T Jtr[8], const T J[8], T residual)
{
    Jtr[0] -= J[0]*residual;
    Jtr[1] -= J[1]*residual;
    Jtr[2] -= J[2]*residual;
    Jtr[3] -= J[3]*residual;
    Jtr[4] -= J[4]*residual;
    Jtr[5] -= J[5]*residual;
    Jtr[6] -= J[6]*residual;
    Jtr[7] -= J[7]*residual;    
}

inline void GaussNewtonOperationJtr8x8_NEON(NFloat Jtr[8], const NFloat J[8], NFloat residual)
{
    __asm__ volatile (
                      // load Jtr into registers
                      \"vld1.32   {d0-d3}, [%0]\\n\\t\"
                      // load J into registers
                      \"vld1.32   {d4-d7}, [%1]\\n\\t\"
                      // load residual in register
                      \"vmov.f32  s16, %2\\n\\t\"
                      // Jtr -= J*residual
                      \"vmls.f32  q0, q2, d8[0]\\n\\t\"
                      \"vmls.f32  q1, q3, d8[0]\\n\\t\"
                      // store result
                      \"vst1.32   {d0-d3}, [%0]\\n\\t\"
                      // output
                      :
                      // input
                      : \"r\"(Jtr), \"r\"(J), \"r\"(residual)
                      // registers
                      : \"d0\", \"d1\", \"d2\", \"d3\", \"d4\", \"d5\", \"d6\", \"d7\", \"d8\", \"d9\", \"d10\", \"d11\", \"d12\", \"d13\", \"d14\"
                      );
}
    
已邀请:
不要使用d8-d15。使用前必须将它们保存在堆栈上。并恢复后。编译器将放置指令来执行此操作,从而浪费了宝贵的时间。 在Jtr之前加载J。预计Jtr的发布阶段会晚于J。 使用VLDMIA / VSTMIA代替VLD / VST。 VLDMIA / VSTMIA速度更快,并且在流水线方面具有优势。 使用向量-向量乘法而不是向量-标量乘法。 如果创建循环版本,请将pld放在开头并展开循环,以便每次迭代从每个指针读取64个字节。 除了我上面提到的那些故障之外(这对于NEON刚接触的人来说是典型的),您的方法很好。您在vmls中找到了最合适的说明。 做得好。 {
__asm__ volatile (
    // load residual in register
    \"vdup.32  q12, %2\\n\\t\"
    // load J into registers
    \"vldmia   %1, {q10-q11}\\n\\t\"
     // load Jtr into registers
    \"vldmia   %0, {q8-q9}\\n\\t\"
    // Jtr -= J*residual
    \"vmls.f32  q8, q10, q12\\n\\t\"
    \"vmls.f32  q9, q11, q12\\n\\t\"
    // store result
    \"vstmia   %0, {q8-q9}\\n\\t\"
    // output
    :
    // input
    : \"r\"(Jtr), \"r\"(J), \"r\"(residual)
    // registers
    : \"q8\", \"q9\", \"q10\", \"q11\", \"q12\"
);
    
编译器本身会优化C代码生成的程序集。它只是不会将一个代码转换为另一个代码。 您要尝试做的是比编译器更好的优化(噢)。您至少知道编译器为上述C代码生成的汇编代码是什么?好吧,如果希望汇编代码更好,就应该这样做。 编辑: 该线程对此类内容进行了很好的讨论: 为什么ARM NEON不比普通C ++快?     
您正在NEON和VFP指令之间切换。在Cortex-A8和A9上都必须这样做。摆脱该VFP vmov.f32指令,并确保该代码不会内联到使用VFP代码的位置,除非长期存在此类代码以证明管道上下文切换是合理的。     
您的C ++版本实际上使用浮点数吗?我不能告诉您,因为您只提供了模板,没有显示您使用的实例化。对于此代码,NEON的速度要比Cortex-A8上的VFP慢得多,这很奇怪,但是对于u32,我看到它可能会以这种方式工作。 我不知道ABI是什么,但是如何传递残差(即,编译器为将其放入该%2寄存器所做的事情)可能会有一些开销。尝试改用指针,然后在单个元素上使用vld1-这样就可以在NEON中仅加载一个浮点数。 如果使用16字节对齐的加载和存储,则可以从阵列中获得更好的性能,但是您可能必须玩一些游戏才能使输入以这种方式工作。不幸的是,您将永远无法获得真正的出色性能,因为您无法避免冗长的vmls指令的大部分延迟(由于将NEON乘法和端到端添加管道相链接)。更糟糕的是,由于从属指令是存储区,因此需要在NEON管道的早期输入它。理想情况下,您一次可以执行多个这些操作,并且可以将多个实例交织在一起-尽可能多地放入寄存器中。     

要回复问题请先登录注册