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

标签 c gcc arm neon intrinsics

我需要为一个项目编写优化的 NEON 代码,我非常乐意编写汇编语言,但为了可移植性/可维护性,我正在使用 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

更多信息:

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

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

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

最佳答案

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

为了避免由于危险而导致的停顿,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-A 架构的处理器上运行良好的计划。

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

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

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

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

关于c - 如何阻止 GCC 破坏我的 NEON 内在函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34901932/

相关文章:

c - 如何让malloc始终返回相同的地址?

C .pc 文件警告

c - 如何修改fork()函数?

c - c中的垃圾收集器用于循环内的变量

c++ - CC、gcc 和 g++ 之间的区别?

assembly - 什么是 cfi_adjust_cfa_offset 和 cfi_rel_offset?

windows - 是否有一种编程方式来确定支持哪些应用程序处理器架构?

android - 在 ARM 模拟器上编译以在 Android 手机上使用

c - 程序运行时出现段错误

C - 数组操作控制台应用程序以状态 0 终止