在 iOS 上使用 NEON 乘积

Posted

技术标签:

【中文标题】在 iOS 上使用 NEON 乘积【英文标题】:Using NEON multiply accumulate on iOS 【发布时间】:2012-10-17 11:05:57 【问题描述】:

即使我只为armv7 编译,NEON 乘法累加内在函数似乎被分解为单独的乘法和加法。

我在 Xcode 最新 4.5 的多个版本、ios SDK 5 到 6 以及不同的优化设置(通过 Xcode 构建和直接通过命令行构建)都经历过这种情况。

例如,构建和拆卸一些包含test.cpp

#include <arm_neon.h>

float32x4_t test( float32x4_t a, float32x4_t b, float32x4_t c )

   float32x4_t result = a;
   result = vmlaq_f32( result, b, c );
   return result;

clang++ -c -O3 -arch armv7 -o "test.o" test.cpp
otool -arch armv7 -tv test.o

结果

test.o:
(__TEXT,__text) section
__Z4test19__simd128_float32_tS_S_:
00000000    f10d0910    add.w   r9, sp, #16 @ 0x10
00000004        46ec    mov ip, sp
00000006    ecdc2b04    vldmia  ip, d18-d19
0000000a    ecd90b04    vldmia  r9, d16-d17
0000000e    ff420df0    vmul.f32    q8, q9, q8
00000012    ec432b33    vmov    d19, r2, r3
00000016    ec410b32    vmov    d18, r0, r1
0000001a    ef400de2    vadd.f32    q8, q8, q9
0000001e    ec510b30    vmov    r0, r1, d16
00000022    ec532b31    vmov    r2, r3, d17
00000026        4770    bx  lr

而不是vmla.f32 的预期用途。

请问我做错了什么?

【问题讨论】:

armcc 或 gcc 按您的预期生成 vmla。 @auselen:那很好,但由于各种原因,我们在为 iOS 移动设备构建时没有该选项:/ 您可以使用 gcc 创建您想要的算法的程序集,然后使用该程序集与 clang :) 【参考方案1】:

这是一个错误或 llvm-clang 的优化。 armcc 或 gcc 会按照您的预期生成 vmla,但如果您阅读 Cortex-A 系列程序员指南 v3,它会说:

20.2.3 调度

在某些情况下,可能会有相当长的延迟,尤其是 VMLA 乘法累加(整数 5 个周期;浮点数 7 个周期)。应该优化使用这些指令的代码,以避免在结果值准备好之前尝试使用它,否则会发生停顿。尽管有几个周期导致延迟,但这些指令确实完全流水线所以几个 操作可以立即进行。

因此,llvm-clang 将 vmla 分离为乘法并累加以填充管道是有意义的。

【讨论】:

嗯,没有意识到这个架构是 7 个周期。将掌握 Cortex-A 指南,而不是依赖过时的知识。 我认为这取决于 cpu 而不是架构。因此,A8 或 A9 之间可能会有所不同。如果您认为这些周期数在某种程度上具有误导性,请随时更新我的​​答案。我个人从不依赖这些周期数,因为我没有在一些特定的处理器库上工作,比如解码视频。我想这类工作需要很多精细的细节,但在通用编程中不会增加太多价值。当然,知道如何在处理单元之间交错指令是件好事…… 周期时间取决于微架构,因此在 armv7 架构的实现之间会有所不同。 clang 不愿意发出 vmla.f32,因为在当前的架构实现中,它获得性能的情况相对有限(例如,它可能会减慢这里的代码)。也就是说,当它的性能模型预测它是有益的时,它肯定会发出指令。 @nguns 是的。我相信任何未准备好的寄存器依赖指令都是如此。有一些指令会很快,有一些指令会更慢。尝试分离寄存器使用之间的依赖关系。例如,代替 "for (..) a += x[i] do "for (..) a += x[i]; b += x[i+1] a += b;" 如果您以正确的方式设置代码并且足够新,编译器应该会为您提供这种矢量化。 @auselen 我直接在汇编中编码,所以碰巧看到它。但似乎是一个很好的尝试,如果我能解决这个问题,会更新你。【参考方案2】:

Neon 乘加指令执行操作

c = c + a * b

请注意,目的地和来源之一是相同的。如果要执行操作

d = c + a * b

编译器必须将其分解为两条指令

d = c
d = d + a * b

另一种,可以分解成乘法+加法指令

d = a * b
d = d + c

在 Cortex-A8/A9 上,两个变体具有相同的吞吐量,但在 Cortex-A8 上,第二个变体的延迟较低,因为乘加指令在许多情况下会导致停顿。

【讨论】:

我希望看到第一个表达式为“c += a * b”。我认为 vmla/s 的固有特性及其文档具有误导性。 请注意,result = result + a * b 正是我给出的示例!我几乎可以说服自己优化器忽略了我指定的内在函数,并根据您所描述的寄存器着色进行某种转换,除了(a)这发生在所有优化级别 - 包括 -O0; (b) 这发生在无处不在,包括执行比我给出的简单示例更重的处理的代码,其中结果显然比使用 MLA 时更糟糕(例如,在执行 4x4 矩阵/矩阵乘法的代码中) - MLA 从字面上看永远不会生成。 @Maratyszcza:我放置了一个更复杂的示例here,希望这表明这里发生的任何事情都不是你描述的优化 @moonshadow 如果您从诸如“vmlaq_f32(result, b, c);”之类的内部调用中删除“result =”部分,则会发生任何变化。如果你完全消除“结果”并“返回一个;”什么时候完成? @auselen:由于内部函数被声明为按值获取参数,因此它们不能影响它们;因此,删除“result =”部分会导致编译器意识到该功能等同于“return a”,并完全优化主体。删除 result 并分配给 a 会产生与以前完全相同的代码。

以上是关于在 iOS 上使用 NEON 乘积的主要内容,如果未能解决你的问题,请参考以下文章

iOS 上 NEON 代码的奇怪结果,跳转到 __ARCLite__load?

无符号字符图像上的快速高斯模糊 - ARM Neon Intrinsics - iOS Dev

二进制图像上的快速像素计数 - ARM neon 内在函数 - iOS 开发

ARM Neon iOS 浮点转换

(int) Image-ARM neon 内在函数上的快速图像正方形 - iOS 开发

使用 NEON 指令进行图像阈值处理