使用内在函数的霓虹灯优化



了解ARM NEON的内在特性时,我编写了一个函数,用于将数组中的元素翻倍。使用内部函数的版本比使用普通C版本的函数花费更多的时间。

Without NEON:

    void  double_elements(unsigned int *ptr, unsigned int size)
 {
        unsigned int loop;
        for( loop= 0; loop<size; loop++)
                ptr[loop]<<=1;
        return;
 }
与霓虹灯:

 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;
}

想知道数组和vector之间的load/store操作是否消耗了更多的时间,从而抵消了并行加法的好处。

更新:更多信息回应Igor的回复。
1.代码张贴在这里:
plain.c
plain.s
neon.c
neon.s
从两个汇编清单中的L7部分(标签)中,我看到霓虹灯版本有更多的汇编指令。(因此需要更多的时间?)
2.我在arm-gcc上使用-mfpu=neon进行编译,没有其他标志或优化。对于普通版本,根本没有编译器标志。
3.这是一个打字错误,SIZE应该是SIZE,两者是一样的。
4、5。在包含4000个元素的数组上尝试。我在函数调用前后分别使用gettimeofday()计时。neon =230us,ordinary=155us。
6.是的,我打印了每个case中的元素。
7.这样做,没有任何改善。

这样的操作可能会运行得更快一些。

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内部参数预加载数据来加快速度。
  • 也正如其他人指出的那样,该操作相当微不足道,您将看到更复杂的操作获得更多收益。

如果你要用内联汇编编写这个,你还可以:

  • 使用对齐的load/stores(我不认为intrinsic可以生成)并确保您的指针始终是128位对齐的,例如vld1.32 {q0}, [r1:128]
  • 你也可以使用后增量版本(我也不确定intrinsic会生成),例如vld1.32 {q0}, [r1:128]!
对于4000个元素,

95us听起来相当慢,在1GHz处理器上,每个128位块约95个周期。假设您正在缓存中工作,您应该能够做得更好。如果你受到外部存储器速度的限制,这个数字就是你所期望的。

这个问题很模糊,你也没有提供太多信息,但我会试着给你一些提示。

  1. 你不知道到底发生了什么,直到你看到组装。用-S,卢克!
  2. 你没有指定编译器设置。你在使用优化吗?循环展开?
  3. 第一个函数使用size,第二个使用SIZE,这是故意的吗?它们是一样的吗?
  4. 您尝试的数组的大小是多少?我不期望NEON对几个元素有任何帮助。
  5. 什么是速度差?几个百分点?几个数量级?
  6. 你检查结果是否一致?你确定代码是相等的吗?
  7. 您使用相同的变量作为中间结果。尝试将添加的结果存储在另一个变量中,这可能会有所帮助(尽管我希望编译器会很聪明并分配不同的寄存器)。此外,您可以尝试使用shift (vshl_n_u32)代替添加。

编辑:谢谢你的回答。我看了一下周围,发现了这个讨论,它说(强调我的):

将数据从NEON寄存器移动到ARM寄存器是Cortex-A8很贵,所以NEON在吗Cortex-A8最适合用于large工作块与小ARM管道相互作用。

在你的情况下,没有NEON到ARM的转换,只有加载和存储。然而,并行操作的节省似乎被非neon部分消耗掉了。我希望在NEON中做很多事情的代码中得到更好的结果,例如颜色转换。

每条指令处理更大的数量,并交错加载/存储和交错使用。该函数当前为双精度(左移)56个单位。

void shiftleft56(const unsigned int* input, unsigned int* output)
{
  __asm__ (
  "vldm %0!, {q2-q8}nt"
  "vldm %0!, {q9-q15}nt"
  "vshl.u32 q0, q2, #1nt"
  "vshl.u32 q1, q3, #1nt"
  "vshl.u32 q2, q4, #1nt"
  "vshl.u32 q3, q5, #1nt"
  "vshl.u32 q4, q6, #1nt"
  "vshl.u32 q5, q7, #1nt"
  "vshl.u32 q6, q8, #1nt"
  "vshl.u32 q7, q9, #1nt"
  "vstm %1!, {q0-q6}nt"
  // "vldm %0!, {q0-q6}nt" if you want to overlap...
  "vshl.u32 q8, q10, #1nt"
  "vshl.u32 q9, q11, #1nt"
  "vshl.u32 q10, q12, #1nt"
  "vshl.u32 q11, q13, #1nt"
  "vshl.u32 q12, q14, #1nt"
  "vshl.u32 q13, q15, #1nt"
  // lost cycle here unless you overlap
  "vstm %1!, {q7-q13}nt"
  : "=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条指令,因此管道永远不会等待其他东西。最后,注意!,它表示后增量。所以它读/写这个值,然后自动从寄存器中增加指针。我建议你不要在ARM代码中使用寄存器,这样它就不会挂起自己的管道…让你的寄存器分开,在ARM端有一个冗余的count变量。

最后一部分…我说的可能是对的,但不总是对的。这取决于当前的Neon版本。时间可能会在未来改变,或者可能不是一直这样。

相关内容

  • 没有找到相关文章

最新更新