使用ARM NEON的8x8浮点32_t矩阵乘法速度较慢?

使用ARM NEON的8x8浮点32_t矩阵乘法速度较慢?,arm,matrix-multiplication,simd,neon,Arm,Matrix Multiplication,Simd,Neon,我想知道是什么内在因素使SIMD比普通矩阵乘法慢,我应该怎么做才能使使用SIMD的大矩阵乘法更快。这里有matrixA[8][8],matrixB[8][8]和结果matrixC[8][8]。因为float32_t的最大元素数是4,所以我使用了2个vmul和vadd,这两个元素似乎没有得到优化。我在ARMv7-A Cortex A8上工作 void matrix_mult_neon (void) { int i; float32x4x2_t vectB1, vectB2, ve

我想知道是什么内在因素使SIMD比普通矩阵乘法慢,我应该怎么做才能使使用SIMD的大矩阵乘法更快。这里有
matrixA[8][8]
matrixB[8][8]
和结果
matrixC[8][8]
。因为float32_t的最大元素数是4,所以我使用了2个vmul和vadd,这两个元素似乎没有得到优化。我在ARMv7-A Cortex A8上工作

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矩阵(void)
{
int i;
浮动32x4x2_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]);
浮动32x4x2_t向量1、向量2、向量3、向量4、向量5、向量6、向量7、向量8;
对于(i=0;i<8;i++)
{
vect1.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]);
vect3.val[0]=vmulq_n_f32(vect3.val[0],matrixA[i][2]);
vect3.val[1]=vmulq_n_f32(vect3.val[1],matrixA[i][2]);
vect4.val[0]=vmulq_n_f32(vectB4.val[0],matrixA[i][3]);
vect4.val[1]=vmulq_n_f32(vect4.val[1],matrixA[i][3]);
vect5.val[0]=vmulq_n_f32(vect5.val[0],matrixA[i][4]);
vect5.val[1]=vmulq_n_f32(vect5.val[1],matrixA[i][4]);
vect6.val[0]=vmulq_n_f32(vect6.val[0],matrixA[i][5]);
vect6.val[1]=vmulq_n_f32(vect6.val[1],matrixA[i][5]);
vect7.val[0]=vmulq_n_f32(vectB7.val[0],matrixA[i][6]);
vect7.val[1]=vmulq_n_f32(vectB7.val[1],matrixA[i][6]);
vect8.val[0]=vmulq_n_f32(vect8.val[0],matrixA[i][7]);
vect8.val[1]=vmulq_n_f32(vect8.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]);
vect1.val[0]=vaddq_f32(vect1.val[0],vect5.val[0]);
vect1.val[0]=vaddq_f32(vect1.val[0],vect6.val[0]);
vect1.val[0]=vaddq_f32(vect1.val[0],vect7.val[0]);
vect1.val[0]=vaddq_f32(vect1.val[0],vect8.val[0]);
vectT1.val[1]=vaddq_f32(vectT1.val[1],vectT2.val[1]);
vect1.val[1]=vaddq_f32(vect1.val[1],vect3.val[1]);
vect1.val[1]=vaddq_f32(vect1.val[1],vect4.val[1]);
vect1.val[1]=vaddq_f32(vect1.val[1],vect5.val[1]);
vect1.val[1]=vaddq_f32(vect1.val[1],vect6.val[1]);
vect1.val[1]=vaddq_f32(vect1.val[1],vect7.val[1]);
vect1.val[1]=vaddq_f32(vect1.val[1],vect8.val[1]);
vst2q_f32(矩阵氖[i],向量TT1);
}
}
我的标准矩阵乘法函数:

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矩阵(void)
{
浮子产品;
int i,j,k;
对于(i=0;i<8;i++)
{
对于(j=0;j<8;j++)
{
tempProduct=0;
对于(k=0;k<8;k++)
{
tempProduct=tempProduct+matrixA[i][k]*matrixB[k][j];
}
matrixC[i][j]=时间积;
}
}
}

我使用库中的函数来计算纳秒时间。

问题:

  • aarch32
    有一个总大小为256字节的霓虹灯寄存器组
  • 8x8浮点矩阵已经有256字节大,您需要三个。(768)
  • 您必须“垂直”读取矩阵B,这意味着物理上不可能通过“流”方式来实现最大的数据局部性
  • 你做向量标量乘法,这需要四倍于向量乘法的总和
  • 通过
    VFP
    加载Mat A。而
    Cortex-A8
    上的
    VFP
    速度特别慢,加上
    NEON
    VFP
    切换开销。与自动矢量化不同的是,Inquired可以按照您要求的方式执行几乎所有操作。你的指示是错误的
解决方案:

我们转置矩阵B,逐行进行点积运算

<>我希望下面的代码对你有用,如果性能很重要,就考虑在汇编中编写,因为编译器在霓虹灯性能上甚至在本质上都是不值得信赖的。
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。(利纳罗GCC 7.1.1)


我要走集合路线。在intrinsics中编写NEON代码纯粹是浪费时间。

比什么慢?你用的是哪种ARM芯片,有哪些编译器选项?也许你的编译器自动矢量化比你手动矢量化要好。还有,你到底是怎么计算时间的?我编辑了这篇文章来澄清。我想知道的是,我在NEON函数中哪里做错了,或者没有进行足够的优化?您使用了什么编译器,以及哪些选项?您是否启用了
-ffast math
?(NEON FP不完全符合IEEE标准,我认为如果没有
-ffast math
,编译器可能会解包为scalar)@PeterCordes你说NEON for AArch32不符合IEEE标准是对的,但这是自动矢量化代码而不是内部代码的问题。Neon intrinsic总是被允许发出相关的Neon指令。@Petercords纯粹是猜测,因为我没有看到这里列出的编译器版本号-但当启用
-ffast math
时,GCC 6似乎会做出一些稍微不同的调度决策。GCC 7生成几乎相同的代码,而不考虑
-ffast math
(对于testcase