2017-11-22 6 views
1

내장 함수가 SIMD를 일반 매트릭스 곱셈보다 느리게 만드는 것은 무엇이며 SIMD를 사용하여 더 빠르게 대 매트릭스의 곱셈을 만들려면 어떻게해야하는지 궁금합니다. 여기에 matrixA[8][8], matrixB[8][8] 및 결과 matrixC[8][8]이 있습니다. float32_t의 최대 요소 수는 4이므로 2 개의 vmul 및 vadd를 수행 했으므로 최적화되지 않은 것 같습니다. 저는 ARMv7-A Cortex A8에서 작업합니다.8x8 float32_t ARM NEON을 사용하는 행렬 곱셈 속도가 느립니까?

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

내 정규 행렬 곱셈 함수 :

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

I 나노초 시간을 계산하기 위해 라이브러리에 <sys/time.h>gettimeofday() 함수를 사용한다.

+0

무엇보다 천천히? 그리고 정확히 정확한 ARM 칩과 컴파일러 옵션은 무엇입니까? 어쩌면 컴파일러가 수동으로 벡터화 한 것보다 자동 벡터화 된 것이 더 나은 것일 수 있습니다. 또한 정확히 얼마나 시간을 보냈습니까? –

+0

나는 분명히하기 위해 게시물을 편집했습니다. 내가 알고 싶은 것은 NEON 함수에서 어디에서 잘못 했습니까, 아니면 충분히 최적화하지 않았습니까? –

+0

어떤 컴파일러를 사용했으며 어떤 옵션이 있습니까? ''양산 '을 사용하셨습니까? (NEON FP는 IEEE와 완전히 호환되지 않습니다. 컴파일러가 스칼라에'-ffast-math'를 쓰지 않고 컴파일 할 수 있습니다.) –

답변

2

문제점 :

  • aarch32는 크기의 NEON 레지스터 뱅크는 총
  • 8 × 8 플로트 행렬은 이미 큰 256 바이트 256 바이트가, 당신은 그 중 세 가지가 필요합니다. (768)
  • 매트릭스 B를 "수직"으로 읽어야합니다. 즉, 최대 데이터 지역성을 위해 "스트리밍"방식을 사용하는 것은 물리적으로 불가능합니다.
  • 벡터 - 벡터 곱셈보다 총 네 배의 벡터 스칼라 곱셈을 사용합니다.
  • 매트 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); 
} 

나는 분해를 확인하고 생성 된 코드는 FUBAR입니다. (Linaro GCC 7.1.1)

나는 조립 경로를 가고 싶다. 내장 함수에 NEON 코드를 작성하는 것은 순수한 시간 낭비입니다.