【问题标题】:Maximum optimization of element wise multiplication via ARM NEON assembly通过 ARM NEON 组件对元素乘法进行最大优化
【发布时间】:2012-09-28 10:52:51
【问题描述】:

我正在为双 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 秒。 (两个线程 - 每个线程计算数组的一半)。天真的解释,一次迭代的计算需要 122 个周期。这似乎有点慢。但瓶颈在哪里?

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

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

【问题讨论】:

  • 您是否仅使用单线程分析了代码?
  • 您能否提供一个类似代码的测试工具,以便人们可以在本地运行它?
  • 新浮动[宽*高]; - 这里操作符“new”不提供对齐的内存。尝试使用静态数组进行测试,或动态分配内存,但必须手动对齐结果指针
  • 您还应该通过使用向量指令移动数据来对内存进行基准测试,以查看可以获得的 peek 值。
  • 是的——通过'new'是动态分配,它由'malloc'和C++构造函数调用组成,但是你可以使用'memalign'函数进行动态对齐分配,例如:size_t alignment = 16; void* ptr = memalign(对齐,mem_size); - 应该够了。之后,您可以在命令中使用 :128 对齐后缀。但这并不能解决您的问题,因为通常它只会提高大约 5% 的性能(在我的测试中)。

标签: c++ optimization assembly arm neon


【解决方案1】:

我对 NEON 的了解并不多。但是,我认为您的数据依赖性会导致性能问题。我建议你用一些负载来启动循环,然后将它们放在 multiplystore 之间。我认为 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 会减少执行时间。您也可以缓存对齐循环。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-04-25
    • 2014-09-18
    • 2019-05-18
    • 1970-01-01
    • 2012-10-10
    • 1970-01-01
    • 1970-01-01
    • 2017-07-02
    相关资源
    最近更新 更多