簡體   English   中英

使用SSE向量指令加速矩陣矩陣乘法

[英]Speed up matrix-matrix multiplication using SSE vector instructions

我在使用SSE矢量指令對某些C代碼進行矢量化時遇到了一些麻煩。 我必須取勝的代碼是

#define N 1000
void matrix_mul(int mat1[N][N], int mat2[N][N], int result[N][N])
{
   int i, j, k;
   for (i = 0; i < N; ++i)
   {
      for (j = 0; j < N; ++j)
      {
         for (k = 0; k < N; ++k)
         {
              result[i][k] += mat1[i][j] * mat2[j][k];
         }
      }
   }
}

這是到目前為止我得到的:

void  matrix_mul_sse(int mat1[N][N], int mat2[N][N], int result[N][N])
{
   int i, j, k; int* l;
   __m128i v1, v2, v3;
   v3 = _mm_setzero_si128();
   for (i = 0; i < N; ++i)
   {
       for (j = 0; j < N; j += 4)
       {

           for (k = 0; k < N; k += 4)
           {

               v1 = _mm_set1_epi32(mat1[i][j]);
               v2 = _mm_loadu_si128((__m128i*)&mat2[j][k]);
               v3 = _mm_add_epi32(v3, _mm_mul_epi32(v1, v2));
               _mm_storeu_si128((__m128i*)&result[i][k], v3);
               v3 = _mm_setzero_si128();
           }
       }
   }
}

執行后,我得到了錯誤的結果。 我知道原因是從內存加載到v2。 我以行主要順序遍歷mat1,因此我需要加載mat2 [0] [0],mat2 [1] [0],mat2 [2] [0],mat2 [3] [0] ....但是實際加載的是mat2 [0] [0],mat2 [0] [1],mat2 [0] [2],mat2 [0] [3] ...,因為mat2已按行主順序存儲在內存中。 我試圖解決此問題,但沒有任何改善。 誰能幫我。

下面修復了您的實現:

void  matrix_mul_sse(int mat1[N][N], int mat2[N][N], int result[N][N])
{
   int i, j, k;
   __m128i v1, v2, v3, v4; 
   for (i = 0; i < N; ++i)
   {
       for (j = 0; j < N; ++j) // 'j' must be incremented by 1
       {
           // read mat1 here because it does not use 'k' index
           v1 = _mm_set1_epi32(mat1[i][j]); 
           for (k = 0; k < N; k += 4)
           {   
               v2 = _mm_loadu_si128((const __m128i*)&mat2[j][k]);

               // read what's in the result array first as we will need to add it later to our calculations
               v3 = _mm_loadu_si128((const __m128i*)&result[i][k]);

               // use _mm_mullo_epi32 here instead _mm_mul_epi32 and add it to the previous result
               v4 = _mm_add_epi32(v3, _mm_mullo_epi32(v1, v2));

               // store the result
               _mm_storeu_si128((__m128i*)&result[i][k], v4);
           }
       }
   }
}

簡而言之, _mm_mullo_epi32 (需要SSE4.1)產生4 x int32結果,而_mm_mul_epi32則產生2 x int64結果。 如果您不能使用SSE4.1,請在此處查看替代SSE2解決方案的答案。

英特爾內部指南的完整描述:

_mm_mullo_epi32:將a和b中的壓縮32位整數相乘,生成中間64位整數,並將中間整數的低32位存儲在dst中。

_mm_mul_epi32:將a和b中每個壓縮的64位元素的低32位整數相乘,並將帶符號的64位結果存儲在dst中。

我對您的代碼進行了一些更改,以使尋址變得明確[在這種情況下有幫助]。

#define N 100

這是向量單位多路累加運算的存根; 您應該能夠用向量單元所拋出的任何距離替換NV,並將相關的操作碼放入此處。

#define NV 8
int Vmacc(int *A, int *B) {
   int i = 0;
   int x = 0;
   for (i = 0; i < NV; i++) {
        x += *A++ * *B++;
    }
    return x;
}

該乘法與標准相比有兩個顯着的變化:1.將列向量矢量緩存到一個連續的向量中。 2.它試圖將乘積的切片推入類似矢量的函數中。 即使不使用向量單元,這也只花費了朴素版本一半的時間,這僅僅是因為更好的緩存/預取利用率。

void mm2(int *A, int *B, int n, int *C) {
    int c, r;
    int stride = 0;
    int cache[N];
    for (c = 0; c < n; c++) {
        /* cache cumn i: */
        for (r = 0; r < n; r++) {
            cache[r] = B[c + r*n];
        }
        for (r = 0; r < n; r++) {
            int k = 0;
            int x = 0;
            int *Av = A + r*n;
            for (k = 0; k+NV-1 < n; k += NV) {
                x += Vmacc(Av+k, cache+k);
            }
            while (k < n) {
                x += Av[k] * cache[k];
                k++;
            }
            C[r*n + c] = x;
        }
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM