C 使用AVX的分片矩阵乘法

C 使用AVX的分片矩阵乘法,c,performance,matrix-multiplication,simd,avx,C,Performance,Matrix Multiplication,Simd,Avx,我编写了下面的C函数,用于使用平铺/分块和AVX向量将两个NxN矩阵相乘,以加快计算速度。现在,当我尝试将AVX内部函数与平铺相结合时,我遇到了一个分割错误。知道为什么会这样吗 另外,矩阵B是否有更好的内存访问模式?也许先换个位置,或者改变k和j循环?因为现在,我正在逐列遍历它,这在空间局部性和缓存线方面可能不是很有效 1 void mmult(double A[SIZE_M][SIZE_N], double B[SIZE_N][SIZE_K], double C[SIZE_M][SIZE_

我编写了下面的C函数,用于使用平铺/分块和AVX向量将两个NxN矩阵相乘,以加快计算速度。现在,当我尝试将AVX内部函数与平铺相结合时,我遇到了一个分割错误。知道为什么会这样吗

另外,矩阵B是否有更好的内存访问模式?也许先换个位置,或者改变k和j循环?因为现在,我正在逐列遍历它,这在空间局部性和缓存线方面可能不是很有效

  1 void mmult(double A[SIZE_M][SIZE_N], double B[SIZE_N][SIZE_K], double C[SIZE_M][SIZE_K])
  2 {
  3   int i, j, k, i0, j0, k0;
  4   // double sum;
  5   __m256d sum;
  6   for(i0 = 0; i0 < SIZE_M; i0 += BLOCKSIZE) {
  7   for(k0 = 0; k0 < SIZE_N; k0 += BLOCKSIZE) {
  8   for(j0 = 0; j0 < SIZE_K; j0 += BLOCKSIZE) {
  9       for (i = i0; i < MIN(i0+BLOCKSIZE, SIZE_M); i++) {
 10         for (j = j0; j < MIN(j0+BLOCKSIZE, SIZE_K); j++) {
 11           // sum = C[i][j];
 12           sum = _mm256_load_pd(&C[i][j]);
 13           for (k = k0; k < MIN(k0+BLOCKSIZE, SIZE_N); k++) {
 14             // sum += A[i][k] * B[k][j];
 15             sum = _mm256_add_pd(sum, _mm256_mul_pd(_mm256_load_pd(&A[i][k]), _mm256_broadcast_sd(&B[k][j])));
 16           }
 17           // C[i][j] = sum;
 18           _mm256_store_pd(&C[i][j], sum);
 19         }
 20       }
 21   }
 22   }
 23   }
 24 }
1无效结果(双A[SIZE\M][SIZE\N]、双B[SIZE\N][SIZE\K]、双C[SIZE\M][SIZE\K])
2 {
3 inti,j,k,i0,j0,k0;
4/双倍和;
5 uu m256d总和;
6用于(i0=0;i0
\u mm256\u load\u pd
是一个对齐要求的加载,但您只需在加载4个双精度32字节向量的最内部循环中按
k++
,而不是
k+=4
。因此,它会出现故障,因为每4个负载中有3个未对准

你不想重复加载,你真正的错误是索引;如果您的输入指针是32字节对齐的,您应该能够继续使用
\u mm256\u load\u pd
而不是
\u mm256\u loadu\u pd
。因此,使用
\u mm256\u load\u pd
成功地捕获了您的错误,而不是工作,但给出了数字错误的结果


您对四个
行*列进行矢量化的策略
点积(生成
C[i][j+0..3]
向量)
应该从四个不同的列(
B[k][j+0..3]
通过
B[k][j]
的向量加载来加载四个连续的双精度,并从
a[i][k]
广播一个双精度。记住你想要4个点积并行

另一种策略可能涉及在末尾将水平和向下转换为标量
C[i][j]+=horizontal\u add(\uu m256d)
,但我认为这需要先转置一个输入,以便行和列向量都在一个点积的连续内存中。但是,在每个内部循环的末尾,需要对水平和进行洗牌

您可能还希望使用至少2个
sum
变量,这样您就可以一次读取整个缓存线,并在内部循环中隐藏FMA延迟,并希望在吞吐量上出现瓶颈或更好的方法是并行处理4或8个向量。因此生成
C[i][j+0..15]
作为
sum0
sum1
sum2
sum3
。(或者使用一个
\uuuum256d
数组;编译器通常会完全展开一个8的循环,并将数组优化为寄存器。)


我认为您只需要5个嵌套循环,就可以阻止行和列。虽然6个嵌套循环显然是一个有效的选项:请查看问题中哪个有5个嵌套循环,而答案中有6个嵌套循环。(只是标量,而不是矢量化)

除了行*列点产品策略之外,这里可能还有其他错误,我不确定


如果您使用的是AVX,您可能也想使用FMA,除非您需要在Sandbybridge/Ivybridge和AMD推土机上运行。(Piledriver和后来的FMA3)


其他matmul策略包括向内环内的目的地添加,以便在内环内加载
C
A
,同时提升
B
的负载。(或者B和交换的,我忘了。)在附录中有一个矢量化缓存阻塞示例,它以这种方式工作,用于SSE2
\uuuu m128d
矢量

@zx485:嗯?注意循环边界:内部循环超过了整个矩阵的块大小分片,允许在L1d缓存中数据仍然处于热状态时重用数据。外部循环使用
+=BLOCKSIZE
。然而,IIRC应该只有5个嵌套循环,而不是6个,细节可能是错误的。@zx485好吧,
BLOCKSIZE
有一个固定的(小)值,比如8或16,而N可能更大。所以,它不是真的O(N^6),但它应该是“通常的”O(N^3)。