C 使用AVX的分片矩阵乘法
我编写了下面的C函数,用于使用平铺/分块和AVX向量将两个NxN矩阵相乘,以加快计算速度。现在,当我尝试将AVX内部函数与平铺相结合时,我遇到了一个分割错误。知道为什么会这样吗 另外,矩阵B是否有更好的内存访问模式?也许先换个位置,或者改变k和j循环?因为现在,我正在逐列遍历它,这在空间局部性和缓存线方面可能不是很有效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_
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)。