使用内在函数进行Neon优化

| 了解ARM NEON内部函数后,我正在计时编写的函数以使数组中的元素加倍。使用内部函数的版本比该函数的普通C版本花费更多的时间。 没有NEON:
    void  double_elements(unsigned int *ptr, unsigned int size)
 {
        unsigned int loop;
        for( loop= 0; loop<size; loop++)
                ptr[loop]<<=1;
        return;
 }
使用NEON:
 void  double_elements(unsigned int *ptr, unsigned int size)
{    
        unsigned int i;
        uint32x4_t Q0,vector128Output;
        for( i=0;i<(SIZE/4);i++)
        {
                Q0=vld1q_u32(ptr);               
                Q0=vaddq_u32(Q0,Q0);
                vst1q_u32(ptr,Q0);
                ptr+=4;

        }
        return;
}
想知道数组和向量之间的加载/存储操作是否正在消耗更多时间,这抵消了并行加法的好处。 更新:更多信息响应Igor的回复。 1.代码发布在这里: 普通的 平原 霓虹灯 霓虹灯 从两个汇编清单的L7部分中,我看到霓虹灯版本的汇编指令数量更多(因此需要更多时间吗?) 2.我在arm-gcc上使用-mfpu = neon进行编译,没有其他标志或优化。对于普通版本,根本没有编译器标志。 3.那是一个错字,SIZE就是大小;两者都是一样的。 4,5。尝试了4000个元素的数组。我在函数调用前后使用gettimeofday()进行计时。NEON= 230us,ordinary = 155us。 6.是的,我在每种情况下都打印了元素。 7.Did,没有任何改善。
已邀请:
问题相当模糊,您没有提供太多信息,但我会尽力为您提供一些指导。 在查看程序集之前,您不确定会发生什么情况。使用-S,卢克! 您没有指定编译器设置。您在使用优化吗?循环展开? 第一个函数使用
size
,第二个函数使用
SIZE
,这是故意的吗?他们是一样的吗? 您尝试过的阵列大小是多少?我不希望NEON在两个方面都提供帮助。 速度差是多少?几个百分点?几个数量级? 您是否检查过结果是否相同?您确定代码是等效的吗? 您正在为中间结果使用相同的变量。尝试将加法结果存储在另一个变量中,这可能会有所帮助(尽管我希望编译器会很聪明,并分配一个不同的寄存器)。另外,您可以尝试使用shift(
vshl_n_u32
)代替加法。 编辑:感谢您的答案。我四处张望,发现了这个讨论,说(强调我的): 将数据从NEON移至ARM寄存器 是Cortex-A8很贵,所以NEON在 Cortex-A8最适合用于大型 小ARM工作块 管道交互。 在您的情况下,没有NEON到ARM的转换,而只能加载和存储。尽管如此,非NEON部件似乎吞噬了并行操作的节省。我希望在NEON中可以完成许多工作的代码中获得更好的结果,例如颜色转换。
这样的事情可能会运行得更快。
void  double_elements(unsigned int *ptr, unsigned int size)
{    
    unsigned int i;
    uint32x4_t Q0,Q1,Q2,Q3;

    for( i=0;i<(SIZE/16);i++)
    {
            Q0=vld1q_u32(ptr);               
            Q1=vld1q_u32(ptr+4);               
            Q0=vaddq_u32(Q0,Q0);
            Q2=vld1q_u32(ptr+8);               
            Q1=vaddq_u32(Q1,Q1);
            Q3=vld1q_u32(ptr+12);               
            Q2=vaddq_u32(Q2,Q2);
            vst1q_u32(ptr,Q0);
            Q3=vaddq_u32(Q3,Q3);
            vst1q_u32(ptr+4,Q1);
            vst1q_u32(ptr+8,Q2);
            vst1q_u32(ptr+12,Q3);
            ptr+=16;

    }
    return;
}
原始代码存在一些问题(优化程序可能会解决一些问题,而其他一些可能无法解决,您需要在生成的代码中进行验证): 添加的结果仅在NEON管道的N3阶段可用,因此以下存储将停止。 假设编译器未展开循环,则循环/分支可能会产生一些开销。 它没有利用通过另一个NEON指令双重发布加载/存储的功能。 如果源数据不在缓存中,则负载将停止。您可以使用__builtin_prefetch内在函数预加载数据以加快此速度。 另外,正如其他人指出的,该操作相当琐碎,对于更复杂的操作,您会看到更多收益。 如果要使用内联汇编编写此代码,则还可以: 使用对齐的加载/存储(我认为内部函数无法生成),并确保您的指针始终保持128位对齐,例如vld1.32 {q0},[r1:128] 您还可以使用postincrement版本(我也不知道内部函数会生成该版本),例如vld1.32 {q0},[r1:128]! 在1GHz处理器上,每128位块约有95个周期,因此4000个元素的95us听起来很慢。假设您正在使用缓存,那么您应该能够做得更好。这个数字是关于如果您受外部存储器速度的约束所期望的。
每条指令的处理量更大,并交错加载/存储和交错使用。该功能当前加倍(向左移动)56 uint。
void shiftleft56(const unsigned int* input, unsigned int* output)
{
  __asm__ (
  \"vldm %0!, {q2-q8}\\n\\t\"
  \"vldm %0!, {q9-q15}\\n\\t\"
  \"vshl.u32 q0, q2, #1\\n\\t\"
  \"vshl.u32 q1, q3, #1\\n\\t\"
  \"vshl.u32 q2, q4, #1\\n\\t\"
  \"vshl.u32 q3, q5, #1\\n\\t\"
  \"vshl.u32 q4, q6, #1\\n\\t\"
  \"vshl.u32 q5, q7, #1\\n\\t\"
  \"vshl.u32 q6, q8, #1\\n\\t\"
  \"vshl.u32 q7, q9, #1\\n\\t\"
  \"vstm %1!, {q0-q6}\\n\\t\"
  // \"vldm %0!, {q0-q6}\\n\\t\" if you want to overlap...
  \"vshl.u32 q8, q10, #1\\n\\t\"
  \"vshl.u32 q9, q11, #1\\n\\t\"
  \"vshl.u32 q10, q12, #1\\n\\t\"
  \"vshl.u32 q11, q13, #1\\n\\t\"
  \"vshl.u32 q12, q14, #1\\n\\t\"
  \"vshl.u32 q13, q15, #1\\n\\t\"
  // lost cycle here unless you overlap
  \"vstm %1!, {q7-q13}\\n\\t\"
  : \"=r\"(input), \"=r\"(output) : \"0\"(input), \"1\"(output)
  : \"q0\", \"q1\", \"q2\", \"q3\", \"q4\", \"q5\", \"q6\", \"q7\",
    \"q8\", \"q9\", \"q10\", \"q11\", \"q12\", \"q13\", \"q14\", \"q15\", \"memory\" );
}
对于Neon优化而言要记住的重要内容...它有两个管道,一个用于装载/存储(带有2条指令队列-一个正在等待执行,一个正在运行-通常每个执行3-9个周期),以及一个用于算术运算(具有2条指令流水线,一条执行并保存结果。)只要您保持这两个管道繁忙并交错您的指令,它就会非常快地工作。更好的是,如果您有ARM指令,那么只要您留在寄存器中,就不必等待NEON完成,它们将同时执行(高速缓存中最多8条指令)!因此,您可以在ARM指令中建立一些基本的循环逻辑,并且它们将同时执行。 您的原始代码也只使用了4个寄存器中的一个(q寄存器具有4个32位值)。其中3个没有明显原因进行了加倍操作,因此您的速度是原来的4倍。 在此代码中最好的办法是对该循环进行处理,方法是在
vstm %1!
之后添加
vldm %0!, {q2-q8}
,从而对它们进行嵌入处理,依此类推。您还会看到我在发送结果之前还要再等待1条指令,因此管道永远不会等待其他指令。最后,注意“ 9”,表示后递增。因此它读取/写入该值,然后自动从寄存器中递增指针。我建议您不要在ARM代码中使用该寄存器,因此它不会挂起自己的管道...使您的寄存器分开,在ARM端有一个冗余的
count
变量。 最后一部分...我说的可能是正确的,但并非总是如此。这取决于您当前的Neon版本。时间可能会在将来发生变化,或者可能并非一直如此。 ymmv,它对我有用。

要回复问题请先登录注册