2017-11-22 47 views
1

どのようなイントリンシックがSIMDを通常の行列乗算よりも遅くするのか、そしてSIMDを使用してより速く大きな行列の乗算を行うために何をすべきかと思います。ここには、matrixA[8][8],matrixB[8][8]と結果matrixC[8][8]があります。 float32_tの最大要素数は4であるため、2つのvmulとvaddを実行しました。これはかなり最適化されていないようです。私はARMv7-A Cortex A8で作業しています。私はナノ秒単位の時間を計算するために、ライブラリ<sys/time.h>gettimeofday()機能を使用8x8 float32_t ARM NEONを使用した行列の乗算は遅くなりますか?

void matrix_mult (void) 
{ 
    float tempProduct; 
    int i, j, k; 

    for (i = 0; i < 8; i++) 
    { 
     for (j = 0; j < 8; j++) 
     { 
      tempProduct = 0; 
      for (k = 0; k < 8; k++) 
      { 
       tempProduct = tempProduct + matrixA[i][k] * matrixB[k][j]; 
      } 
      matrixC[i][j] = tempProduct; 
     } 
    } 
} 

void matrix_mult_neon (void) 
{ 
    int i; 

    float32x4x2_t vectB1, vectB2, vectB3, vectB4, vectB5, vectB6, vectB7, vectB8; 
    vectB1 = vld2q_f32(matrixB[0]); 
    vectB2 = vld2q_f32(matrixB[1]); 
    vectB3 = vld2q_f32(matrixB[2]); 
    vectB4 = vld2q_f32(matrixB[3]); 
    vectB5 = vld2q_f32(matrixB[4]); 
    vectB6 = vld2q_f32(matrixB[5]); 
    vectB7 = vld2q_f32(matrixB[6]); 
    vectB8 = vld2q_f32(matrixB[7]); 


    float32x4x2_t vectT1, vectT2, vectT3, vectT4, vectT5, vectT6, vectT7, vectT8; 
    for (i = 0; i < 8; i++) 
    { 
     vectT1.val[0] = vmulq_n_f32(vectB1.val[0], matrixA[i][0]); 
     vectT1.val[1] = vmulq_n_f32(vectB1.val[1], matrixA[i][0]); 
     vectT2.val[0] = vmulq_n_f32(vectB2.val[0], matrixA[i][1]); 
     vectT2.val[1] = vmulq_n_f32(vectB2.val[1], matrixA[i][1]); 
     vectT3.val[0] = vmulq_n_f32(vectB3.val[0], matrixA[i][2]); 
     vectT3.val[1] = vmulq_n_f32(vectB3.val[1], matrixA[i][2]); 
     vectT4.val[0] = vmulq_n_f32(vectB4.val[0], matrixA[i][3]); 
     vectT4.val[1] = vmulq_n_f32(vectB4.val[1], matrixA[i][3]); 
     vectT5.val[0] = vmulq_n_f32(vectB5.val[0], matrixA[i][4]); 
     vectT5.val[1] = vmulq_n_f32(vectB5.val[1], matrixA[i][4]); 
     vectT6.val[0] = vmulq_n_f32(vectB6.val[0], matrixA[i][5]); 
     vectT6.val[1] = vmulq_n_f32(vectB6.val[1], matrixA[i][5]); 
     vectT7.val[0] = vmulq_n_f32(vectB7.val[0], matrixA[i][6]); 
     vectT7.val[1] = vmulq_n_f32(vectB7.val[1], matrixA[i][6]); 
     vectT8.val[0] = vmulq_n_f32(vectB8.val[0], matrixA[i][7]); 
     vectT8.val[1] = vmulq_n_f32(vectB8.val[1], matrixA[i][7]); 


     vectT1.val[0] = vaddq_f32(vectT1.val[0], vectT2.val[0]); 
     vectT1.val[0] = vaddq_f32(vectT1.val[0], vectT3.val[0]); 
     vectT1.val[0] = vaddq_f32(vectT1.val[0], vectT4.val[0]); 
     vectT1.val[0] = vaddq_f32(vectT1.val[0], vectT5.val[0]); 
     vectT1.val[0] = vaddq_f32(vectT1.val[0], vectT6.val[0]); 
     vectT1.val[0] = vaddq_f32(vectT1.val[0], vectT7.val[0]); 
     vectT1.val[0] = vaddq_f32(vectT1.val[0], vectT8.val[0]); 

     vectT1.val[1] = vaddq_f32(vectT1.val[1], vectT2.val[1]); 
     vectT1.val[1] = vaddq_f32(vectT1.val[1], vectT3.val[1]); 
     vectT1.val[1] = vaddq_f32(vectT1.val[1], vectT4.val[1]); 
     vectT1.val[1] = vaddq_f32(vectT1.val[1], vectT5.val[1]); 
     vectT1.val[1] = vaddq_f32(vectT1.val[1], vectT6.val[1]); 
     vectT1.val[1] = vaddq_f32(vectT1.val[1], vectT7.val[1]); 
     vectT1.val[1] = vaddq_f32(vectT1.val[1], vectT8.val[1]); 

     vst2q_f32(matrixC_neon[i], vectT1); 
    } 
} 

私の通常の行列乗算機能。

+0

何よりも遅い?そして、正確なARMチップとは何か、そしてどのコンパイラオプションを使っていましたか?おそらく、コンパイラが手動でベクトル化するよりも自動ベクトル化がうまくいくかもしれません。また、どのように正確に時間を計ったのですか? –

+0

私は明確にするために投稿を編集しました。私が知りたいことは、NEON関数のどこで間違っているのか、それとも十分に最適化していないのかです。 –

+0

どのコンパイラを使用しましたか、どのようなオプションがありますか?あなたは '-phast-math'を有効にしましたか? (NEON FPは完全にIEEEに準拠していないので、コンパイラがスカラーに '-ffast-math'なしで解凍するかもしれないと思います) –

答えて

2

問題:

  • aarch32は、サイズのNEONレジスタバンクは合計
  • 8×8のフロート行列がすでにある大型の256バイト256バイトがあり、あなたはそれらの3を必要とします。 (768)
  • 行列Bを「垂直に」読む必要があります。つまり、データの局所性を最大限にするために「ストリーミング」方法を実行することは物理的に不可能です。
  • ベクトル - ベクトル乗算の合計の4倍のベクトルスカラー乗算を行います。
  • マットAはVFPで読み込みます。そして、Cortex-A8VFPは特に、信じられないほど遅く、NEON < - >VFPのスイッチングオーバーヘッドに追加されています。自動ベクトル化とは異なり、組み込み関数は、あなたがそれを行うようにすべてのことを行います。そしてあなたは間違った指示をしました。

ソリューション:

我々は行列Bを転置し、ラインでドット積の数学の行を行います。

パフォーマンスが重要な場合は、組み込み関数であってもNEONのパフォーマンスに関してコンパイラはあまり信頼できないため、アセンブリで記述することを検討してください。

static __always_inline float32x2_t dotProduct(float32x4x2_t input1, float32x4x2_t input2) 
{ 
    float32x2_t d0, d1; 
    float32x4_t q0; 
    input1.val[0] = vmulq_f32(input1.val[0], input2.val[0]); 
    input1.val[1] = vmulq_f32(input1.val[1], input2.val[1]); 

    q0 = vaddq_f32(input1.val[0], input1.val[1]); 
    d0 = vget_low_f32(q0); 
    d1 = vget_high_f32(q0); 
    d0 = vpadd_f32(d0, d1); 
    d0 = vpadd_f32(d0, d1); 
    return d0; 
} 

void matMulF_neon(float *pDst, float *pMatA, float *pMatB) 
{ 
    float32x4x4_t line01, line23, line45, line67; 
    float32x4x2_t b[8], *pA, *pB, temp; 
    float32x2x4_t result; 
    uint32_t  i; 

    // vld4 for easier transpose 
    line01 = vld4q_f32(pMatB++); 
    line23 = vld4q_f32(pMatB++); 
    line45 = vld4q_f32(pMatB++); 
    line67 = vld4q_f32(pMatB); 

    // transpose MatB 
    vuzpq_f32(line01.val[0], line45.val[0]); 
    vuzpq_f32(line01.val[1], line45.val[1]); 
    vuzpq_f32(line01.val[2], line45.val[2]); 
    vuzpq_f32(line01.val[3], line45.val[3]); 

    vuzpq_f32(line23.val[0], line67.val[0]); 
    vuzpq_f32(line23.val[1], line67.val[1]); 
    vuzpq_f32(line23.val[2], line67.val[2]); 
    vuzpq_f32(line23.val[3], line67.val[3]); 

    // store MatB to stack 
    b[0].val[0] = line01.val[0]; 
    b[0].val[1] = line01.val[1]; 
    b[1].val[0] = line01.val[2]; 
    b[1].val[1] = line01.val[3]; 
    b[2].val[0] = line23.val[0]; 
    b[2].val[1] = line23.val[1]; 
    b[3].val[0] = line23.val[2]; 
    b[3].val[1] = line23.val[3]; 

    b[4].val[0] = line45.val[0]; 
    b[4].val[1] = line45.val[1]; 
    b[5].val[0] = line45.val[2]; 
    b[5].val[1] = line45.val[3]; 
    b[6].val[0] = line67.val[0]; 
    b[6].val[1] = line67.val[1]; 
    b[7].val[0] = line67.val[2]; 
    b[7].val[1] = line67.val[3]; 

    pA = (float32x4x2_t *) pMatA; 
    i = 8; 
    do 
    { 
     // just the right amount of data for aarch32 NEON register bank size 
     pB = b; 
     temp = *pA++; 
     result.val[0] = dotProduct(*pB++, temp); 
     result.val[1] = dotProduct(*pB++, temp); 
     result.val[2] = dotProduct(*pB++, temp); 
     result.val[3] = dotProduct(*pB++, temp); 
     vst4_lane_f32(pDst++, result, 0); 

     result.val[0] = dotProduct(*pB++, temp); 
     result.val[1] = dotProduct(*pB++, temp); 
     result.val[2] = dotProduct(*pB++, temp); 
     result.val[3] = dotProduct(*pB, temp); 
     vst4_lane_f32(pDst++, result, 0); 
    } while (--i); 
} 

/////////////////////////// EDIT

私は解体をチェックし、生成されたコードはFUBARです。 (Linaro GCC 7.1.1)

私は組立ルートに行きます。組み込み関数でNEONコードを書くことは、時間IMOの純粋な無駄です。

関連する問題