2017-11-22 13 views
1

Ich frage mich, was intrinsics machen die SIMD langsamer als normale Matrix-Multiplikation und was soll ich tun, um die Multiplikation von großen Matrix schneller mit SIMD. Hier haben wir matrixA[8][8], matrixB[8][8] und Ergebnis matrixC[8][8]. Da die maximale Anzahl von Elementen für float32_t 4 ist, habe ich 2 vmul und vadd gemacht, die scheinbar nicht optimiert sind. Ich arbeite an ARMv7-A Cortex A8.8x8 float32_t Matrixmultiplikation mit ARM NEON ist langsamer?

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); 
    } 
} 

Meine normale Matrixmultiplikation Funktion:

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; 
     } 
    } 
} 

Ich benutze gettimeofday() Funktion in der Bibliothek <sys/time.h> Zeit in Nanosekunden zu berechnen.

+0

Langsamer als was? Und welchen exakten ARM-Chip hast du benutzt und mit welchen Compiler-Optionen? Vielleicht hat sich Ihr Compiler besser vektorisiert, als Sie manuell vektorisiert haben. Wie genau hast du es auch gemacht? –

+0

Ich habe den Beitrag zur Klärung bearbeitet. Was ich wissen möchte ist, dass wo in der NEON-Funktion habe ich falsch gemacht oder nicht genug optimiert? –

+0

Welchen Compiler haben Sie verwendet und welche Optionen? Hast du '-fast-math' aktiviert? (NEON FP ist nicht vollständig IEEE-konform, und ich denke, ohne "-ffast-math" könnte der Compiler zu Skalar entpacken) –

Antwort

2

Das Problem:

  • aarch32 hat eine NEON Registerbank von der Größe insgesamt 256 Bytes
  • A 8x8 float Matrix bereits ist 256 Bytes groß, und Sie müssen drei von ihnen. (768)
  • Sie müssen die Matrix B "vertikal" lesen, was bedeutet, dass es physisch unmöglich ist, den "Streaming" Weg für maximale Datenlokalität zu machen.
  • Sie tun vektor-skalare Multiplikation, die vier mal so viel Gesamt als Vektor-Vektor-Multiplikation dauert.
  • Sie laden Mat A über VFP. Und VFP auf der Cortex-A8 ist besonders unglaublich langsam, zusätzlich zu NEON ->VFP Switching Overhead. Im Gegensatz zu Auto-Vectorization, Intrinsic tun so ziemlich alles, was Sie sagen, es zu tun. Und du hast die falsche Anweisung gegeben.

Die Lösung:

Wir Matrix B transponieren und tun Mathe Linie Punkt-Produkt für Zeile.

Ich hoffe, dass der folgende Code für Sie funktioniert, und wenn die Leistung von entscheidender Bedeutung ist, schreiben Sie in Assembly, da Compiler nicht sehr vertrauenswürdig sind, wenn es um NEON-Leistung geht, auch in intrinsics.

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

überprüfte ich die Demontage und der generierte Code ist FUBAR. (Linaro GCC 7.1.1)

Ich würde die Montage Route gehen. Das Schreiben von NEON-Codes in Eigen- schaften ist reine Zeitverschwendung IMO.

Verwandte Themen