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

时间:2011-05-17 17:51:20

标签: assembly arm neon

我正在尝试使用NEON对iPhone ARM上的特定问题实施Gauss-Newton优化。下面的第一个函数是我原来的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"
                      );
}

4 个答案:

答案 0 :(得分:5)

  1. 请勿使用d8-d15。它们必须在使用前保存在堆叠上。然后恢复。编译器将执行此操作,浪费宝贵的周期。
  2. 在Jtr之前加载J.预计Jtr将在后期的管道阶段而不是J。
  3. 使用VLDMIA / VSTMIA代替VLD / VST。 VLDMIA / VSTMIA速度更快,并且具有管道优势。
  4. 使用矢量矢量乘法代替矢量标量乘法。
  5. 如果您创建循环版本,请将pld放在开头并展开循环,以便每次迭代从每个指针读取64字节。
  6. 除了上面提到的那些错误 - 这对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"
    );
    

答案 1 :(得分:3)

编译器本身优化了C代码生成的程序集。它只是不会将一个代码翻译成另一个代码。

你要做的是比编译器做更好的优化(哦)。您是否至少知道编译器为上面的C代码生成的汇编代码是什么?好吧,如果你希望你的汇编代码更好,你应该这样做。

修改

这个帖子对这类东西进行了很好的讨论: Why ARM NEON not faster than plain C++?

答案 2 :(得分:3)

您正在切换NEON和VFP指令。在Cortex-A8和A9上执行此操作都会受到惩罚。摆脱该VFP vmov.f32指令,并确保此代码不会内联到使用VFP代码的位置,除非有很长的此类代码来证明管道上下文切换。

答案 3 :(得分:1)

您的C ++版本是否实际使用了浮点数?我无法分辨,因为您只提供了模板而没有显示您使用的实例化。非常奇怪的是,对于这段代码,NEON在Cortex-A8上的速度要比VFP大得多,但对于u32s,我可以看到它可能会以这种方式运行。

我不知道ABI是什么,但是残差的传递可能会有一些开销(也就是说,编译器正在做什么才能将它传入%2寄存器)。尝试使用指针代替并在单元素上使用vld1 - 你可以用NEON加载一个浮点数。

如果使用16字节对齐的加载和存储,您将从阵列中获得更好的性能,但是您可能必须玩一些游戏才能使输入以这种方式工作。不幸的是,你永远不会得到非常好的性能,因为你没有避免vmls指令的大部分延迟很长(由于链接NEON乘法和端到端添加管道)。由于依赖指令是一个存储,它需要在NEON管道的早期输入,因此情况更糟。理想情况下,您一次可以执行多个这样的操作,并且可以将多个实例交错在一起 - 尽可能多地插入寄存器。