首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >SSE矩阵-矩阵乘法

SSE矩阵-矩阵乘法
EN

Stack Overflow用户
提问于 2016-10-28 21:29:11
回答 2查看 6.5K关注 0票数 3

在C语言中,我很难用SSE做矩阵乘法。

到目前为止,我得到的是:

代码语言:javascript
复制
#define N 1000

void matmulSSE(int mat1[N][N], int mat2[N][N], int result[N][N]) {
  int i, j, k;
  __m128i vA, vB, vR;

  for(i = 0; i < N; ++i) {
    for(j = 0; j < N; ++j) {
        vR = _mm_setzero_si128();
        for(k = 0; k < N; k += 4) {
            //result[i][j] += mat1[i][k] * mat2[k][j];
            vA = _mm_loadu_si128((__m128i*)&mat1[i][k]);
            vB = _mm_loadu_si128((__m128i*)&mat2[k][j]); //how well does the k += 4 work here? Should it be unrolled?
            vR = _mm_add_epi32(vR, _mm_mul_epi32(vA, vB));
        }
        vR = _mm_hadd_epi32(vR, vR);
        vR = _mm_hadd_epi32(vR, vR);
        result[i][j] += _mm_extract_epi32(vR, 0);
    }
  }
}

我似乎不能给出正确的结果。我是不是遗漏了什么?搜索剂量似乎有很大帮助-每个结果要么只做4x4矩阵,magic或一些特殊的魔术,不太容易读和难以理解.

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2016-10-30 06:55:55

你说得对,你的vB才是问题所在。您正在加载4个连续整数,但mat2[k+0..3][j]并不是连续的。你实际上得到了mat2[k][j+0..3]

我忘了什么对母校有用。有时,它可以并行地产生4个结果,而不是对每个结果进行水平求和。

转置你的一个输入矩阵工作,并花费O(N^2)。这是值得的,因为它意味着O(N^3) matmul可以使用顺序访问,而您当前的循环结构变得SIMD友好。

还有更好的方法,例如在使用前就将小块转置,所以当您再次读取它们时,它们仍然是L1缓存中的热点。或者在目标行上循环并添加一个结果,而不是为单个或小的行*列点产品积累完整的结果。缓存阻塞,也就是循环平铺,是良好的匹配性能的关键之一。还请参阅What Every Programmer Should Know About Memory?,它在附录中有一个缓存阻塞的SIMD示例,没有转置。

关于优化矩阵乘法,使用SIMD和缓存阻塞已经写了很多.我建议你搜索一下。大多数情况下,如果它可能是在谈论FP,但它也同样适用于整数。

(除了SSE/AVX只对FP有FMA,对于32位整数没有FMA,而且8位和16位输入PMADD指令可以水平地添加对。)

实际上,我认为您可以在这里并行地生成4个结果,如果一个输入已被转换为,则为

代码语言:javascript
复制
void matmulSSE(int mat1[N][N], int mat2[N][N], int result[N][N]) {

  for(int i = 0; i < N; ++i) {
    for(int j = 0; j < N; j+=4) {   // vectorize over this loop
        __m128i vR = _mm_setzero_si128();
        for(int k = 0; k < N; k++) {   // not this loop
            //result[i][j] += mat1[i][k] * mat2[k][j];
            __m128i vA = _mm_set1_epi32(mat1[i][k]);  // load+broadcast is much cheaper than MOVD + 3 inserts (or especially 4x insert, which your new code is doing)
            __m128i vB = _mm_loadu_si128((__m128i*)&mat2[k][j]);  // mat2[k][j+0..3]
            vR = _mm_add_epi32(vR, _mm_mullo_epi32(vA, vB));
        }
        _mm_storeu_si128((__m128i*)&result[i][j], vR));
    }
  }
}

一个广播负载(或单独的load+broadcast没有AVX)仍然比一个集合便宜得多。

当前的代码使用4个插入来完成收集,而不是通过对第一个元素使用MOVD来破坏上一个迭代值上的依赖链,这就更糟了。但是,与load + PUNPCKLDQ相比,即使是4个分散元素的最佳集合也是相当糟糕的。更不用说,这使得您的代码需要SSE4.1。

尽管_mm_mullo_epi32仍然需要SSE4.1,而不是扩展的)

请注意,整数乘吞吐量通常比FP乘差,特别是在Haswell和以后。FMA单元每个32位元素只有24位宽乘子(对于FMA),所以使用那些用于32x32=>32位整数的单元需要分裂成两个uop。

票数 3
EN

Stack Overflow用户

发布于 2019-11-18 03:04:59

这个第一个版本是OP发布的,作为对不属于哪里的问题的编辑。把它转移到一个社区-维基的答案只是为了子孙后代。

第一个版本对于性能来说是绝对的垃圾,是矢量化的最糟糕的方式,在最内循环中做一个hsum到标量,并且使用insert_epi32进行手动收集,甚至不是一个4x4转置。

更新: Woho!我终于想出来了。除了我逻辑中的错误(感谢Peter的帮助),还有_mm_mul_epi32()不像我想的那样工作的问题--我应该使用_mm_mullo_epi32()

我知道这不是最有效的代码,但它是为了让它首先正常工作-现在我可以继续优化它。

(注意,不要用这个,它非常慢)

代码语言:javascript
复制
// editor's note: this is the most naive and horrible way to vectorize
void matmulSSE_inefficient(int mat1[N][N], int mat2[N][N], int result[N][N]) {
    int i, j, k;
    __m128i vA, vB, vR, vSum;

    for(i = 0; i < N; ++i) {
        for(j = 0; j < N; ++j) {
            vR = _mm_setzero_si128();
            for(k = 0; k < N; k += 4) {
                //result[i][j] += mat1[i][k] * mat2[k][j];
                vA = _mm_loadu_si128((__m128i*)&mat1[i][k]);
                          // less braindead would be to start vB with movd, avoiding a false dep and one shuffle uop.
                          // vB = _mm_cvtsi32_si128(mat2[k][j]);   // but this manual gather is still very bad
                vB = _mm_insert_epi32(vB, mat2[k][j], 0);     // false dependency on old vB
                vB = _mm_insert_epi32(vB, mat2[k + 1][j], 1);  // bad spatial locality
                vB = _mm_insert_epi32(vB, mat2[k + 2][j], 2);  // striding down a column
                vB = _mm_insert_epi32(vB, mat2[k + 3][j], 3);
                vR = _mm_mullo_epi32(vA, vB);
                vR = _mm_hadd_epi32(vR, vR);                // very slow inside the inner loop
                vR = _mm_hadd_epi32(vR, vR);
                result[i][j] += _mm_extract_epi32(vR, 0);

                //DEBUG
                //printf("vA: %d, %d, %d, %d\n", vA.m128i_i32[0], vA.m128i_i32[1], vA.m128i_i32[2], vA.m128i_i32[3]);
                //printf("vB: %d, %d, %d, %d\n", vB.m128i_i32[0], vB.m128i_i32[1], vB.m128i_i32[2], vB.m128i_i32[3]);
                //printf("vR: %d, %d, %d, %d\n", vR.m128i_i32[0], vR.m128i_i32[1], vR.m128i_i32[2], vR.m128i_i32[3]);
                //printf("\n");
            }
        }
    }
}

最初由OP编写的效率极低的代码的端

更新2:将OP的示例转换为i循环顺序版本。vR需要额外的负载,并将存储移到内部循环中,但是设置vA可以向上移动一个循环。结果更快。

代码语言:javascript
复制
// this is significantly better but doesn't do any cache-blocking
void matmulSSE_v2(int mat1[N][N], int mat2[N][N], int result[N][N]) {
    int i, j, k;
    __m128i vA, vB, vR;

    for(i = 0; i < N; ++i) {
        for(k = 0; k < N; ++k) {
            vA = _mm_set1_epi32(mat1[i][k]);
            for(j = 0; j < N; j += 4) {
                //result[i][j] += mat1[i][k] * mat2[k][j];
                vB = _mm_loadu_si128((__m128i*)&mat2[k][j]);
                vR = _mm_loadu_si128((__m128i*)&result[i][j]);
                vR = _mm_add_epi32(vR, _mm_mullo_epi32(vA, vB));
                _mm_storeu_si128((__m128i*)&result[i][j], vR);

                //DEBUG
                //printf("vA: %d, %d, %d, %d\n", vA.m128i_i32[0], vA.m128i_i32[1], vA.m128i_i32[2], vA.m128i_i32[3]);
                //printf("vB: %d, %d, %d, %d\n", vB.m128i_i32[0], vB.m128i_i32[1], vB.m128i_i32[2], vB.m128i_i32[3]);
                //printf("vR: %d, %d, %d, %d\n", vR.m128i_i32[0], vR.m128i_i32[1], vR.m128i_i32[2], vR.m128i_i32[3]);

                //printf("\n");
            }
        }
    }
}

假设N为4的倍数,矢量宽度为

如果不是这样的话,通常更容易将数组存储设置为向量宽度的倍数,因此在每一行的末尾都有填充,您可以只使用简单的j < N; j += 4循环条件。您将希望将实际的N大小与存储布局保持单独的跟踪,其行距为4或8的倍数。

否则,您需要一个循环条件,比如j < N-3;j += 4‘,以及行尾的标量清理。

或者在寄存器中掩蔽或保存最后一个完整的向量,这样您就可以使用可能重叠的最终向量(结束在行的末尾)来_mm_alignr_epi8,并且可以进行向量存储。这是更容易与AVX或特别是AVX512掩蔽。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/40313434

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档