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

Assembly NEON ASM代码运行速度比C代码慢得多?,assembly,arm,neon,Assembly,Arm,Neon,我正在尝试使用NEON在iPhone ARM上实现一个特定问题的Gauss-Newton优化。下面的第一个函数是我原来的C函数。第二个是我写的NEON asm代码。我每次都运行了100000次,NEON版本比C版本要长7-8倍。我认为加载(vld1.32)占用了大部分时间。我尝试着删除一些指令 有人对这个问题有什么见解吗?谢谢 template<class T> inline void GaussNewtonOperationJtr8x8(T Jtr[8], const T J[8]

我正在尝试使用NEON在iPhone ARM上实现一个特定问题的Gauss-Newton优化。下面的第一个函数是我原来的C函数。第二个是我写的NEON asm代码。我每次都运行了100000次,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"
                      );
}
模板
内联无效高斯净操作jtr8x8(T Jtr[8],常数T J[8],T残差)
{
Jtr[0]-=J[0]*残差;
Jtr[1]=J[1]*残差;
Jtr[2]=J[2]*残差;
Jtr[3]=J[3]*残差;
Jtr[4]=J[4]*残差;
Jtr[5]-=J[5]*残差;
Jtr[6]=J[6]*残差;
Jtr[7]-=J[7]*残差;
}
内联无效高斯净操作JTR8x8_NEON(NFloat Jtr[8],常数NFloat J[8],NFloat残差)
{
__反复无常(
//将Jtr加载到寄存器中
vld1.32{d0-d3},[%0]\n\t
//将J加载到寄存器中
vld1.32{d4-d7},[%1]\n\t
//在寄存器中加载残差
vmov.f32 s16,%2\n\t
//Jtr-=J*残差
vmls.f32 q0、q2、d8[0]\n\t
vmls.f32 q1、q3、d8[0]\n\t
//存储结果
vst1.32{d0-d3},[%0]\n\t
//输出
:
//输入
:“r”(Jtr)、“r”(J)、“r”(残差)
//登记册
:“d0”,“d1”,“d2”,“d3”,“d4”,“d5”,“d6”,“d7”,“d8”,“d9”,“d10”,“d11”,“d12”,“d13”,“d14”
);
}

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

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

编辑:

这篇文章对这类东西进行了很好的讨论:
您正在霓虹灯和VFP指令之间切换。这样做会对大脑皮层A8和A9造成惩罚。除去VFPvMOV.F32指令,同时确保代码不被嵌入到使用VFP代码的地方,除非有长时间运行这样的代码来证明流水线上下文切换。

< P>你的C++版本实际上是使用浮标吗?我不知道,因为您只提供了模板,没有显示您使用的实例化。对于这段代码,NEON会比Cortex-A8上的VFP慢很多,这很奇怪,但对于U32,我可以看到它可能是这样工作的

我不知道ABI是什么,但是在如何传递剩余值方面可能会有一些开销(也就是说,编译器是如何将其放入%2寄存器的)。尝试改用指针,并在单个元素上使用vld1,这样可以只加载一个浮点

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

  • 不要使用d8-d15。在使用之前,必须将其保存在烟囱上。之后又恢复了。编译器会将指令放入其中,从而浪费宝贵的周期
  • 在Jtr之前加载J。Jtr预计比J
  • 使用VLDMIA/VSTMIA代替VLD/VST。VLDMIA/VSTMIA速度更快,在流水线方面具有优势
  • 使用向量乘法而不是向量标量乘法
  • 如果创建循环版本,请将pld放在开始处并展开循环,以便每次迭代从每个指针读取64字节
  • 除了我上面提到的那些缺点之外——这对于新接触霓虹灯的人来说很典型——你的方法非常好。您在vmls中找到了最合适的指令

    干得好

    {


    我还没有看到GCC编译器生成NEON代码。所以我自己正在尝试生成ASM NEON代码,并与C代码进行比较。我更仔细地阅读了这个链接。所以我想我的示例使用NEON不会表现得很好?我移动了指令以删除依赖项,但没有任何改进。时间差是多少一次执行之间的差异(毫秒)(非100.000)在你的C代码和你设计的程序集之间?我还没有找到一个足够高分辨率的计时器,可以在iPhone上测试一次迭代。我找到了:有两种方法可以实现。谢谢。有没有其他方法可以将一个单精度数字输入霓虹灯寄存器?我需要得到“剩余值”将参数登记到寄存器中,使它成为两个浮点数的第一个,并将其装入D登记器。一般来说,双浮点和四浮点运算是氖,单浮点运算是VFP。是的,C++版本是用浮点。
    __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"
    );