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")
您可以从编译器输出中看到,GCC 发出fmla同时执行乘法和加法的指令。这比fmul/fadd单独进行更快、更准确。因此,如果 C 函数和汇编函数之间的输出存在差异,人们会认为汇编函数会给出更糟糕的答案。例如,您应该能够通过执行以四精度(long double在 ARM64 上)或任意精度数学包计算结果的测试来确认这一点。
我不确定为什么您想要故意使 C 函数表现更糟,但如果您这样做,您可以使用该选项-ffp-contract=off来禁用融合乘加。 参见 Godbolt。
附带说明一下,如果您希望编译器生成像您一样的矢量化代码,则需要至少将指针参数声明c为restrict(当然还要确保c数组永远不会重叠a或b)。否则,GCC 将测试别名,并在需要时回退到非矢量化版本,请参阅 godbolt。
您可能还想升级您的编译器。上面的链接是 GCC 11.1 的,GCC 7.3 生成的代码仍然是矢量化的,但要复杂得多。