在 ARMv8 环境中使用向量寄存器 (NEON) 的集合时,双精度不适用

Posted

技术标签:

【中文标题】在 ARMv8 环境中使用向量寄存器 (NEON) 的集合时,双精度不适用【英文标题】:When using an assembly of vector registers(NEON) in ARMv8 environment, double-precision does not apply 【发布时间】:2021-09-27 10:05:49 【问题描述】:

我在 ARMv8 环境中使用双精度数据类型进行编码。当给定优化选项-O3时,用C语言实现的值与在Assembly(NEON)中使用ARMv8指令的值不同。单独使用 FMUL 和 FADD 时,取值相同,但同时使用 FMUL + FADD 时,结果值与 C 语言不同。我们想解决这个问题。

这里是汇编文件

.data

.text
.global Asm_Operation_Test

Asm_Operation_Test:

MOV x3,#0

Operation_Loop:

LD1 v0.2d-v3.2d,[x0],#64
LD1 v4.2d-v7.2d,[x1],#64

FMUL v0.2d,v0.2d,v4.2d
FADD v0.2d,v0.2d,v4.2d

FMUL v1.2d,v1.2d,v5.2d
FADD v1.2d,v1.2d,v5.2d

FMUL v2.2d,v2.2d,v6.2d
FADD v2.2d,v2.2d,v6.2d

FMUL v3.2d,v3.2d,v7.2d
FADD v3.2d,v3.2d,v7.2d

ST1 v0.2d-v3.2d,[x2],#64
ADD x3,x3,#1
CMP x3,#32
BNE Operation_Loop

ret

这里是 C 文件

typedef struct double v; fpr;

C_Operation Test(fpr*a, fpr*b, fpr*c)
    for(int i=0; i<256; i++)
   
      c[i].v = a[i].v * b[i].v + b[i].v;
    

汇编函数和 C 函数执行相同的操作。输入数据为double类型,输入256个随机数(double)的数组。如果加上 gcc -O0 选项,这两个函数的结果是完全一样的。但是执行gcc-O3时,两个函数的结果值并不完全相同,只有12位十进制与单精度相同。我们想知道其中的原因。

我们的比较功能很简单。 if( ((double) a[i].v != (double)b​​[i].v)) printf("Error\n")

【问题讨论】:

您能否添加有关该问题的更多详细信息?例如,您能否展示问题中提到的 C 代码行、汇编行和值以及使用的编译器? 使用FMA指令之类的声音。检查编译器供应商提供的文档。可能有一个选项可以强制对所有子表达式进行正确舍入。 我附上了代码和其他解释。我们也使用 Jetson Xavier (ARMv8.2),我们使用 gcc 7.5.0。我们确认 ARMv8 浮点指令集支持双精度。 C 标准不要求 C 运算符和 IEEE-754 浮点算术运算之间直接对应,也不要求在计算浮点表达式时使用标称精度。在 C 中,a*b + c 可以编译为 ab 的乘法(四舍五入到类型中可表示的最接近的值),然后是 c 的加法(也四舍五入)或融合乘法- add(计算单个结果 a•b+c 就好像最后只有一个舍入)。此外,float 表达式可以使用double 类型等计算。 因此,如果您出于某种原因想要将您的程序集与 C 代码相匹配,您可以编译 C 代码并向编译器发出请求以显示生成的程序集(在 GCC 和 Clang 中为-S ) 然后重写您的程序集以匹配。但通常没有理由这样做。您要解决的实际问题是什么?您真的需要程序集与 C 完全匹配吗?为什么?还是您担心不匹配表明计算中存在一些错误? 【参考方案1】:

您可以从the compiler output 看到,GCC 发出fmla 指令来同时进行乘法和加法。这比单独执行fmul/fadd 更快更准确。因此,如果 C 函数和您的汇编函数之间的输出存在差异,人们会认为是您的汇编函数给出了更糟糕的答案。您应该能够确认这一点,例如,通过执行以四精度(ARM64 上的long double)或任意精度数学包计算结果的测试。

我不知道你为什么要故意让 C 函数的行为变得更糟,但如果你这样做了,你可以使用选项 -ffp-contract=off 来禁用融合乘加。 See on godbolt.

附带说明,如果您希望编译器生成像您这样的矢量化代码,您至少需要将指针参数 c 声明为 restrict(当然要确保 c 数组永远不会重叠ab)。否则 GCC 将测试别名并在需要时回退到非矢量化版本,see on godbolt。

您可能还想升级您的编译器。上面的链接是 GCC 11.1 的,GCC 7.3 生成的代码是still vectorized but much more complicated。

【讨论】:

以上是关于在 ARMv8 环境中使用向量寄存器 (NEON) 的集合时,双精度不适用的主要内容,如果未能解决你的问题,请参考以下文章

Armv8a NEON 内联汇编代码:如何将 16x8 位向量转换为四个 4x32 位(整数)向量?

将 NEON 组件与非向量函数混合

使用 NEON 在 ARM 汇编中对四字向量中的所有元素求和

在 ARM Cortex A8 上的汇编中 XOR NEON 向量/寄存器的所有元素/通道(成对?)

如何在 ARMv7 NEON 向量之间移动单字数据

在 NEON armv8 程序集中存储指令