使用ARM NEON进行矩阵乘法运算

1.背景

        最近想要体验一下ARM Helium (MVE)对SIMD指令的支持,于是到网络上想要购买一款搭载ARMv8-M架构CPU的开发板,然而看中的板子需要较长的交货周期,于是暂时放弃。因为NEON和Helium在设计理念上有诸多相似之处,之前笔者对于NEON也不是非常熟悉,所以尝试用NEON进行一个简单的3x3矩阵乘法运算,以对其有个初步的了解,这里顺带做个记录,分享一下。

2. 计算内容

        这里做个最简单的计算。

 求b与a的乘积。

3.代码

        时间有限,用AI先生成一段代码进行计算,结果发现有坑,代码需要调整才能用,调整后的代码如下:

#include <stdio.h>

#include <arm_neon.h>

 

/**

 * matrix_multiply_neon

 */

void matrix_multiply_neon(float32_t *a, float32_t *b, float32_t *c)

{

    float32x4_t a0 = vld1q_f32(a);

    float32x4_t a1 = vld1q_f32(a + 3);

    float32x4_t a2 = vld1q_f32(a + 6);

 

    float32x4_t b0 = vld1q_f32(b);

    float32x4_t b1 = vld1q_f32(b + 3);

    float32x4_t b2 = vld1q_f32(b + 6);

 

    float32x4_t c0 = vmovq_n_f32(0);

    float32x4_t c1 = vmovq_n_f32(0);

    float32x4_t c2 = vmovq_n_f32(0);

 

    c0 = vfmaq_laneq_f32(c0, a0, b0, 0);

    c0 = vfmaq_laneq_f32(c0, a1, b0, 1);

    c0 = vfmaq_laneq_f32(c0, a2, b0, 2);

 

    c1 = vfmaq_laneq_f32(c1, a0, b1, 0);

    c1 = vfmaq_laneq_f32(c1, a1, b1, 1);

    c1 = vfmaq_laneq_f32(c1, a2, b1, 2);

 

    c2 = vfmaq_laneq_f32(c2, a0, b2, 0);

    c2 = vfmaq_laneq_f32(c2, a1, b2, 1);

    c2 = vfmaq_laneq_f32(c2, a2, b2, 2);

 

    vst1q_f32(c, c0);

    vst1q_f32(c + 3, c1);

    vst1q_f32(c + 6, c2);

}

 

/**

 * main

 */

int main()

{

    int i, j;

    float32_t a[10] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};

    float32_t b[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};

    float32_t c[10] = {0};

 

    matrix_multiply_neon(a, b, c);

 

    printf("Result:\n");

 

    for (i = 0; i < 3; i++) {

        for (j = 0; j < 3; j++)

            printf(" %f ", c[i * 3 + j]);

        printf("\n");

    }

 

    return 0;

}

        AI生成的代码会出现调用vst1q_f32向数组c写入越界的问题,这里需要将a,b,c(尤其是c)三个数组声明大一些。原因是vst1q_f32写入的对象是float32x4_t,是4个单精度浮点数。

4.说明

        这里的矩阵乘法运算主要依赖vfmaq_laneq_f32这个API。

        这个API的原型定义为:

        向量b中的每个标量和向量v中lane作为index下标取出的标量数字相乘,计算结果和向量a中的每个标量相加并将计算结果存储在a中。

        需要分解来看。

        上面的矩阵相乘,展开后是:

        上面标红的部分可以体现出对[0][0]这个标量进行的一次计算,对应代码是:

        c0 = vfmaq_laneq_f32(c0, a0, b0, 0);

        之后是第二次计算:

        c0 = vfmaq_laneq_f32(c0, a0, b0, 0);

        第三次:

        c0 = vfmaq_laneq_f32(c0, a2, b0, 2);

        三次计算累加后就得到了第一行的计算结果,30,24和18。

        实机测试结果为:

5.总结

        NEON本质上是利用SIMD的优势加快向量计算,这种计算相较于仅用一般计算指令进行计算一定可以节省更多用于load/store的时钟周期进而加快运算速度(理论上计算规模越大越明显)。但是相较于一些专用加速器在特定领域应该还相对逊色。总之,这种计算方式可以在很多计算场景体现出优势。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值