为何C++ SSE SIMD与Go汇编实现浮点数组求和结果不一致?
问题分析:C++ SSE与Go汇编求和结果不一致
问题背景
用C++结合SSE指令实现两个float数组的逐元素求和后累加,输出结果为33.32;但相同逻辑的Go汇编实现输出异常值1.1076422e+09,需排查差异原因。
通用伪代码逻辑:
a := [N]float{} b := [N]float{} sum := 0 for i < N do sum += a[i] + b[i] endfor
Go汇编代码的核心错误
1. 数组指针步长计算错误
Go汇编中使用ADDQ $10, AX和ADDQ $10, CX移动数组指针,这是完全错误的:
- 每个
float32占4字节,一次处理4个元素需要移动4*4=16字节,而非10字节。 - 错误步长会导致指针越界访问无关内存,读取垃圾数据,最终计算出异常大的数值。
2. 未处理非4倍数的剩余元素
当数组长度N=7时,循环每次处理4个元素,仅能覆盖前4个元素,剩余3个元素完全被忽略,直接导致求和结果缺失部分数据。
修正后的Go汇编代码
#include "textflag.h" //func _sum(a []float32, b[]float32) float32 TEXT _sum(SB), NOSPLIT, $0-52 MOVQ a_base+0(FP), AX MOVQ b_base+24(FP), CX MOVQ a_len+8(FP), DX VXORPS X0, X0, X0 // 初始化累加寄存器为0 loop: CMPQ DX, $4 JB handle_remainder // 剩余元素不足4个,跳转到处理剩余逻辑 VMOVUPS (AX), X1 // 加载a的4个元素 VMOVUPS (CX), X2 // 加载b的4个元素 VADDPS X2, X1, X1 // 计算a[i]+b[i] VADDPS X1, X0, X0 // 累加到总和寄存器 ADDQ $16, AX // 指针移动16字节(4个float32) ADDQ $16, CX SUBQ $4, DX JMP loop handle_remainder: CMPQ DX, $0 JE back // 逐个处理剩余元素 VMOVSS (AX), X1 VMOVSS (CX), X2 VADDSS X2, X1, X1 VADDSS X1, X0, X0 ADDQ $4, AX ADDQ $4, CX SUBQ $1, DX JMP handle_remainder back: // 将XMM0中的低32位(总和)存入返回值 MOVSS X0, ret+48(FP) RET
额外注意点
- Go主文件中
b数组的第二个元素值为1,而C++中是2,这也会导致结果差异,需保持测试数据一致。 - C++原代码也存在越界加载问题(当长度非4倍数时,
_mm_loadu_ps会读取数组外的内存),建议同样添加剩余元素处理逻辑,避免未定义行为。
内容的提问来源于stack exchange,提问作者ANTON SAVELIEV
相关产品推荐
相关产品推荐

