如何阻止 GCC 破坏我的 NEON 内在函数?

How to stop GCC from breaking my NEON intrinsics?

我需要为一个项目编写优化的 NEON 代码,我非常乐意编写汇编语言,但是对于 portability/maintainability 我使用的是 NEON 内部函数。此代码需要尽可能快,因此我利用我在 ARM 优化方面的经验来正确交错指令并避免管道停顿。无论我做什么,GCC 都会对我不利,并创建充满停顿的较慢代码。

有谁知道如何让 GCC 摆脱困境并将我的内在函数转换为代码?

这是一个例子:我有一个简单的循环,它否定并复制浮点值。它一次使用 4 组 4 组,以便有时间加载内存和执行指令。剩下的寄存器很多,所以没有理由把事情搞得这么糟。

float32x4_t f32_0, f32_1, f32_2, f32_3;
int x;
for (x=0; x<n-15; x+=16)
{
   f32_0 = vld1q_f32(&s[x]);
   f32_1 = vld1q_f32(&s[x+4]);
   f32_2 = vld1q_f32(&s[x+8]);
   f32_3 = vld1q_f32(&s[x+12]);
   __builtin_prefetch(&s[x+64]);
   f32_0 = vnegq_f32(f32_0);
   f32_1 = vnegq_f32(f32_1);
   f32_2 = vnegq_f32(f32_2);
   f32_3 = vnegq_f32(f32_3);
   vst1q_f32(&d[x], f32_0);
   vst1q_f32(&d[x+4], f32_1);
   vst1q_f32(&d[x+8], f32_2);
   vst1q_f32(&d[x+12], f32_3);
} 

这是它生成的代码:

vld1.32 {d18-d19}, [r5]
vneg.f32  q9,q9        <-- GCC intentionally causes stalls
add r7,r7,#16
vld1.32 {d22-d23}, [r8]
add r5,r1,r4
vneg.f32 q11,q11   <-- all of my interleaving is undone (why?!!?)
add r8,r3,#256
vld1.32 {d20-d21}, [r10]
add r4,r1,r3
vneg.f32 q10,q10
add lr,r1,lr
vld1.32 {d16-d17}, [r9]
add ip,r1,ip
vneg.f32 q8,q8

更多信息:

当我在 ASM 代码中编写循环时完全按照我的内在函数模式(甚至没有使用额外的 src/dest 寄存器来获得一些免费的 ARM 周期),它仍然比 GCC 的代码快。

更新: 我很欣赏 James 的回答,但总的来说,它并不能真正帮助解决问题。使用 cortex-a7 选项时,我的最简单函数的性能稍好一些,但大多数函数没有变化。可悲的事实是 GCC 对内部函数的优化不是很好。几年前,当我使用 Microsoft ARM 编译器时,它始终如一地为 NEON 内在函数创建精心制作的输出,而 GCC 却始终如履薄冰。使用 GCC 4.9.x,什么都没有改变。我当然很欣赏 GCC 的 FOSS 性质和 GNU 的更大努力,但不可否认的是,它不如英特尔、微软甚至 ARM 的编译器做得好。

广义上,您在此处看到的 class 优化被称为 "instruction scheduling"。 GCC 使用指令调度来尝试为程序的每个基本块中的指令构建更好的调度。在这里,"schedule" 指的是块中指令的任何正确排序,而 "better" 调度可能是一种避免停顿和其他流水线危险的调度,或者是一种减少变量有效范围的调度(导致在更好的寄存器分配中)或指令中的其他一些排序目标。

为了避免由于危险而导致的停顿,GCC 使用了您所针对的处理器的流水线模型(参见 here for details of the specification language used for these, and here 示例流水线模型)。该模型对处理器功能单元的 GCC 调度算法以及这些功能单元上指令的执行特性给出了一些指示。 GCC 然后可以调度指令,以最大限度地减少由于多个指令需要相同处理器资源而导致的结构性危害。

没有 -mcpu-mtune 选项(对编译器),或 --with-cpu,或 --with-tune 选项(对编译器的配置),GCC对于 ARM 或 AArch64 将尝试为您的目标架构修订使用代表性模型。在这种情况下,-march=armv7-a 会导致编译器尝试安排指令,就像在命令行上传递 -mtune=cortex-a8 一样。

所以你在输出中看到的是 GCC 试图将你的输入转换成它期望在 运行 在 Cortex-A8 上运行时能够很好执行的计划,并且 运行 合理在实现 ARMv7-A 架构的处理器上表现良好。

要对此进行改进,您可以尝试:

  • 明确设置您的目标处理器(-mcpu=cortex-a7
  • 完全禁用指令调度 (`-fno-schedule-insns -fno-schedule-insns2)

请注意,完全禁用指令调度很可能会导致您在其他地方出现问题,因为 GCC 将不再尝试减少代码中的流水线风险。

编辑 关于您的编辑,可以在 GCC Bugzilla 中报告 GCC 中的性能错误(参见 https://gcc.gnu.org/bugs/ ),就像正确性错误一样。当然,所有优化都涉及一定程度的启发式,编译器可能无法击败经验丰富的汇编程序员,但如果编译器正在做一些特别令人震惊的事情,则值得强调。