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

时间:2016-01-20 13:53:16

标签: c gcc arm neon intrinsics

我需要为项目编写优化的NEON代码,并且我非常乐意编写汇编语言,但是为了便携性/可维护性,我使用了NEON instrinsics。此代码需要尽可能快,因此我使用我在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

更多信息:

  • GCC 4.9.2 for Raspbian
  • 编译器标志:-c -fPIE -march=armv7-a -Wall -O3 -mfloat-abi=hard -mfpu=neon

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

更新:我感谢James&#39;回答,但在事情的方案中,它并没有真正帮助解决问题。使用cortex-a7选项,我最简单的函数表现得更好,但大多数人看不到任何变化。令人遗憾的是,GCC对内在函数的优化并不是很好。几年前,当我使用Microsoft ARM编译器时,它始终为NEON内在函数创建精心设计的输出,而GCC始终绊倒。有了GCC 4.9.x,一切都没有改变。我当然很欣赏海湾合作委员会的FOSS性质和更大的GNU努力,但无可否认,它不像英特尔,微软甚至ARM的编译器那样做得好。

1 个答案:

答案 0 :(得分:9)

从广义上讲,您在此处看到的优化类别称为&#34;指令调度&#34;。 GCC使用指令调度来尝试为程序的每个基本块中的指令构建更好的计划。这是一个&#34;时间表&#34;指的是块中指令的任何正确排序,以及更好的&#34;时间表可以是避免失速和其他管道危险的时间表,或者可以减少变量的有效范围(导致更好的寄存器分配)或者指令上的某些其他排序目标的时间表。

为了避免因危险而导致的停顿,GCC使用您要定位的处理器的管道模型(有关用于这些的规范语言的详细信息,请参阅here,并使用here作为示例管道模型)。该模型给出了处理器功能单元的GCC调度算法的一些指示,以及这些功能单元上的指令的执行特性。然后,GCC可以安排指令,以最大限度地减少因多条指令需要相同处理器资源而导致的结构性危险。

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

因此,您在输出中看到的是GCC尝试将您的输入转换为在Cortex-A8上运行时期望执行良好的计划,并且在实现ARMv7的处理器上运行得相当好 - 建筑。

要改进这一点,您可以尝试:

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

请注意,完全禁用指令调度可能会在其他地方引起您的问题,因为GCC将不再尝试减少代码中的管道危险。

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