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

You*_*Kim 0 floating-point precision assembly neon armv8

我正在 ARMv8 环境中使用双数据类型进行编码。当给出优化选项-O3时,用C语言实现的值与在汇编(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
Run Code Online (Sandbox Code Playgroud)

这里是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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

我们的比较函数很简单。if( (double) a[i].v != (double)b​​[i].v)) printf("错误\n")

Nat*_*dge 6

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

我不确定为什么您想要故意使 C 函数表现更糟,但如果您这样做,您可以使用该选项-ffp-contract=off来禁用融合乘加。 参见 Godbolt

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

您可能还想升级您的编译器。上面的链接是 GCC 11.1 的,GCC 7.3 生成的代码仍然是矢量化的,但要复杂得多