通过ARM NEON组件最大限度地优化元素乘法

时间:2012-10-08 07:54:15

标签: c++ optimization assembly arm neon

我正在为双Cortex-A9处理器优化两个单维数组的元素乘法。 Linux正在运行,我正在使用GCC 4.5.2编译器。

以下是我的C ++内联汇编程序函数。 src1,src2和dst是16字节对齐的。

更新:可测试代码:

void Multiply(
    const float* __restrict__ src1,
    const float* __restrict__ src2,
    float* __restrict__ dst,
    const unsigned int width,
    const unsigned int height)
{
    int loopBound = (width * height) / 4;
    asm volatile(
        ".loop:                             \n\t"
        "vld1.32  {q1}, [%[src1]:128]!      \n\t"
        "vld1.32  {q2}, [%[src2]:128]!      \n\t"
        "vmul.f32 q0, q1, q2                \n\t"
        "vst1.32  {q0}, [%[dst]:128]!       \n\t"
        "subs     %[lBound], %[lBound], $1  \n\t"
        "bge      .loop                     \n\t"
        :
        :[dst] "r" (dst), [src1] "r" (src1), [src2] "r" (src2),
        [lBound] "r" (loopBound)
        :"memory", "d0", "d1", "d2", "d3", "d4", "d5
    );
}

//The following function describes how to test the element wise multiplication
void Test()
{
    const unsigned int width = 1024, height = 1024;
    float* src1 __attribute__((aligned(16))) = new float[width * height];
    float* src2 __attribute__((aligned(16))) = new float[width * height];
    float* dst __attribute__((aligned(16))) = new float[width * height];
    for(unsigned int i = 0; i < (width * height); i++)
    {
        src1[i] = (float)rand();
        src2[i] = (float)rand();
    }
    Multiply(src1, src2, dst, width, height);

    std::cout << dst[0] << std::endl;
}

1024 * 1024值的计算需要~0.016 s。 (两个线程 - 每个线程计算一半的数组)。天真地解释,一次迭代的计算需要122个周期。这似乎有点慢。但瓶颈在哪里?

我甚至尝试使用pld命令预加载L2缓存中的元素,通过计算每次迭代最多20个值来“展开”循环,并重新排序指令以使处理器不等待内存。我的速度没有那么快(最快加快0.001秒)。

您对加快计算有什么建议吗?

1 个答案:

答案 0 :(得分:1)

我对NEON的了解并不多。但是,我认为您有数据依赖性会导致性能问题。我建议你用一些加载来填充循环,然后将它们放在 multiply store 之间。我认为 store 可能会阻塞,直到 multiply 完成。

    asm volatile(
    "vld1.32  {q1}, [%[src1]:128]!      \n\t"
    "vld1.32  {q2}, [%[src2]:128]!      \n\t"
    ".loop:                             \n\t"
    "vmul.f32 q0, q1, q2                \n\t"
    "vld1.32  {q1}, [%[src1]:128]!      \n\t"
    "vld1.32  {q2}, [%[src2]:128]!      \n\t"
    "vst1.32  {q0}, [%[dst]:128]!       \n\t"
    "subs     %[lBound], %[lBound], $1  \n\t"
    "bge      .loop                     \n\t"
    :
    :[dst] "r" (dst), [src1] "r" (src1), [src2] "r" (src2),
    [lBound] "r" (loopBound)
    :"memory", "d0", "d1", "d2", "d3", "d4", "d5
);

这样你就可以用乘法并行加载。您将需要过度分配源数组或更改循环索引并进行最终的乘法和存储。如果NEON操作不影响条件代码,您也可以重新订购 subs 并将其放置得更早。

编辑实际上, Cortex A-9媒体处理引擎文档建议交错使用ARM和NEON指令,因为它们可以并行执行。此外,NEON指令似乎设置 FPSCR 而不是ARM CPSR ,因此重新排序subs会减少执行时间。您也可以缓存对齐循环。