ARM GCC有bug吗?使用vldr链代替一个vldmia...

6
考虑以下使用NEON优化的函数:
void mat44_multiply_neon(float32x4x4_t& result, const float32x4x4_t& a, const float32x4x4_t& b) {
    // Make sure "a" is mapped to registers in the d0-d15 range,
    // as requested by NEON multiply operations below:
    register float32x4_t a0 asm("q0") = a.val[0];
    register float32x4_t a1 asm("q1") = a.val[1];
    register float32x4_t a2 asm("q2") = a.val[2];
    register float32x4_t a3 asm("q3") = a.val[3];
    asm volatile (
    "\n\t# multiply two matrices...\n\t"
    "# result (%q0,%q1,%q2,%q3)  = first column of B (%q4) * first row of A (q0-q3)\n\t"
    "vmul.f32 %q0, %q4, %e8[0]\n\t"
    "vmul.f32 %q1, %q4, %e9[0]\n\t"
    "vmul.f32 %q2, %q4, %e10[0]\n\t"
    "vmul.f32 %q3, %q4, %e11[0]\n\t"
    "# result (%q0,%q1,%q2,%q3) += second column of B (%q5) * second row of A (q0-q3)\n\t"
    "vmla.f32 %q0, %q5, %e8[1]\n\t"
    "vmla.f32 %q1, %q5, %e9[1]\n\t"
    "vmla.f32 %q2, %q5, %e10[1]\n\t"
    "vmla.f32 %q3, %q5, %e11[1]\n\t"
    "# result (%q0,%q1,%q2,%q3) += third column of B (%q6) * third row of A (q0-q3)\n\t"
    "vmla.f32 %q0, %q6, %f8[0]\n\t"
    "vmla.f32 %q1, %q6, %f9[0]\n\t"
    "vmla.f32 %q2, %q6, %f10[0]\n\t"
    "vmla.f32 %q3, %q6, %f11[0]\n\t"
    "# result (%q0,%q1,%q2,%q3) += last column of B (%q7) * last row of A (q0-q3)\n\t"
    "vmla.f32 %q0, %q7, %f8[1]\n\t"
    "vmla.f32 %q1, %q7, %f9[1]\n\t"
    "vmla.f32 %q2, %q7, %f10[1]\n\t"
    "vmla.f32 %q3, %q7, %f11[1]\n\t\n\t"
    : "=&w"  (result.val[0]), "=&w"  (result.val[1]), "=&w"  (result.val[2]), "=&w" (result.val[3])
    : "w"   (b.val[0]),      "w"   (b.val[1]),      "w"   (b.val[2]),      "w"   (b.val[3]),
      "w"   (a0),            "w"   (a1),            "w"   (a2),            "w"   (a3)
    :
    );
}

为什么GCC 4.5会生成这样的代码来加载第一个矩阵:

vldmia  r1, {d0-d1}
vldr    d2, [r1, #16]
vldr    d3, [r1, #24]
vldr    d4, [r1, #32]
vldr    d5, [r1, #40]
vldr    d6, [r1, #48]
vldr    d7, [r1, #56]

...而不是仅仅:

vldmia  r1, {q0-q3}

我使用的选项:

arm-none-eabi-gcc-4.5.1 -x c++ -march=armv7-a -mcpu=cortex-a8 -mfpu=neon -mfloat-abi=softfp -O3 -ffast-math -fgcse-las -funsafe-loop-optimizations -fsee -fomit-frame-pointer -fstrict-aliasing -ftree-vectorize

请注意,使用iPhoneOS提供的编译器会产生相同的结果:
/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc-4.2 -x c++ -arch armv7 -mcpu=cortex-a8 -mfpu=neon -mfloat-abi=softfp -O3 -ffast-math -fgcse-las -funsafe-loop-optimizations -fsee -fomit-frame-pointer -fstrict-aliasing -ftree-vectorize

3
请前往http://gcc.gnu.org/bugzilla报告错误,正是这样的错误报告才能改进GCC。 - ismail
3个回答

6
简单回答:
GCC编译器目前在生成ARM代码方面表现不佳。如果你仔细观察其他代码,你会发现GCC几乎从不安排它可以使用多个寄存器的加载/存储,除了硬编码的地方,如函数prolog / epilog和inline memcpy。
当涉及到使用Neon指令时,代码变得更糟。这与NEON单元的工作方式有关:您可以将寄存器对视为四重或双倍字。这是(据我所知)GCC支持的体系结构中寄存器使用的独特特性。因此,代码生成器在所有实例中都不能生成最佳代码。
顺便说一下:在此过程中,GCC不知道在Cortex-A8上使用“免费”的移位器功能对寄存器调度产生重要影响,而且GCC大多数情况下都做错了。

1
这与您提供的代码片段无关,但在实际的NEON代码中,将vld1分成128位或256位块可能会导致性能更好的代码。这是因为NEON加载、存储(以及置换)可以与其他NEON指令同时执行,但双重发行只能发生在多周期指令的第一或最后一个周期。如果对齐,则可以在1个周期内获得128位负载,在2个周期内获得256位负载。

1

PPC 有类似的指令 (ldmwstmw),在某些架构上执行相当一系列的加载/存储指令比其还要慢。显然,你可能会权衡指令高速缓存空间或其他考虑因素。你应该在目标 ARM 平台上进行测试,以确定 gcc 是否真的“错误”。


在ARM Cortex-A8上,LDM从第三个寄存器开始执行效果更好。哦 - 这有助于保持代码的小巧,由于使用较少的代码缓存而产生了一个不错的次要效果。 - Nils Pipenbrinck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接