C Simd matmul程序给出了不同的数值结果

C Simd matmul程序给出了不同的数值结果,c,floating-point,vectorization,simd,avx,C,Floating Point,Vectorization,Simd,Avx,我正在尝试使用simd intrinsic用C编写矩阵乘法程序。我很确定我的实现,但是当我执行时,我得到一些数值错误,从结果矩阵系数的第五位开始 REAL_T只是一个带有typedef的浮点 /* This is my matmul Version with simd, using floating simple precision*/ void matmul(int n, REAL_T *A, REAL_T *B, REAL_T *C){ int i,j,k; __m256 vA, v

我正在尝试使用simd intrinsic用C编写矩阵乘法程序。我很确定我的实现,但是当我执行时,我得到一些数值错误,从结果矩阵系数的第五位开始

REAL_T只是一个带有typedef的浮点

/* This is my matmul Version with simd, using floating simple precision*/
void matmul(int n, REAL_T *A, REAL_T *B, REAL_T *C){
  int i,j,k;
  __m256 vA, vB, vC, vRes;
  for (i=0; i<n; i++){
    for (j=0; j<n; j++){  
      for (k=0; k<n; k= k+8){
        vA = _mm256_load_ps(&A[i*n+k]);
        vB = _mm256_loadu_ps(&B[k*n+j]);
        vC = _mm256_mul_ps(vA, vB);
        vC = _mm256_hadd_ps(vC, vC);
        vC = _mm256_hadd_ps(vC, vC);
        /*To get the resulting coefficient, after doing 2 hadds,
        I have to get the first and the last element of the resulting
        Vector vC*/
        C[i*n+j] += ((float )(vC[0])) + ((float )(vC[7]));
      } /* for k */
    } /* for j */
  } /* for i */
}
*/End of program
但是,对于simd版本,正方形为:

+6.916510e+01  +6.916510e+01  
+5.918463e+01  +5.918463e+01  

+7.946147e+00  +7.946147e+00  
+7.936355e+00  +7.936355e+00 
如图所示,两个版本之间存在数值错误。
任何帮助都将不胜感激

这看起来很正常;按不同顺序添加数字会在临时表中产生不同的舍入

FP数学不具有关联性;如果进行优化,则会改变结果。1/

变化量取决于数据。对于
float
,只有小数点后第5位的差异似乎是合理的


除非您采取了特殊的数字预防措施,如先将小数字相加,否则顺序结果不是“更正确”,它们只是有不同的错误。

事实上,使用多个累加器通常会提高大型列表的精度,前提是您的数字都具有相似的大小。(理想情况下是多个SIMD向量,每个向量由多个元素组成,以隐藏FP add或FMA延迟)。 是一种数字技术,它将这一点带到了下一个层次:对树中列表的子集求和,以避免将单个数组元素添加到更大的值中。比如说,

使用固定数量的累加器(例如8x
\uuuu m256
=64
float
累加器)可能会将预期误差减少64倍,而不是从N到log N进行完全成对求和


脚注1:并行化、SIMD和多累加器需要关联性。

例如,在具有4周期延迟2个时钟吞吐量FMA且SIMD宽度为8个浮点数的机器上,即具有AVX2的Skylake系统,潜在加速比为4*2=8(来自多个累加器),8*8(来自SIMD宽度),乘以核心数,而不是纯顺序版本,即使是对于可能不太准确而只是不同的问题

大多数人认为一个因子<代码> 8×8=64 < /代码>值得!(理论上,你也可以在四核上并行计算另一个因子,可能是4,假设大矩阵的比例是完美的)

您已经在使用
float
而不是
double
来提高性能

有关使用多个累加器隐藏FMA延迟以减少速度的更多信息,请参见,这将暴露8加速的其他因素


另外,不要在最内部的循环中使用
hadd
。垂直求和,并在循环结束时使用有效的缩减。(). 您确实希望避免编译器在每一步都将向量提取为标量,这会破坏SIMD的大部分好处!除此之外,
hadd
不值得用于1个向量的水平和;它需要在所有现有的CPU上进行2次洗牌+一次常规的
add

非常感谢您给出了清晰的答案!我将改变我的hadd实现,因为我不知道它有这样的效果。。。
/*The matrix are initialized as follows*/
  for (i = 0; i < n; i++)
    for (j = 0; j < n; j++){
      *(A+i*n+j) = 1 / ((REAL_T) (i+j+1));
      *(B+i*n+j) = 1.0;
      *(C+i*n+j) = 1.0;
    }
/*End of initialization*/
+6.916512e+01  +6.916512e+01  
+5.918460e+01  +5.918460e+01  

+7.946186e+00  +7.946186e+00  
+7.936391e+00  +7.936391e+00  
+6.916510e+01  +6.916510e+01  
+5.918463e+01  +5.918463e+01  

+7.946147e+00  +7.946147e+00  
+7.936355e+00  +7.936355e+00