ARM Neon 编程笔记一(ARM NEON Intrinsics, SIMD运算, 优化心得)

本文详细介绍了ARM NEON Intrinsics的使用,包括如何入门、进阶和深入理解NEON编程。通过矩阵乘法的C、NEON及内联汇编实现对比,展示了NEON加速的效果,并提供了相关资源和优化心得。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. ARM Neon Intrinsics 编程

1.入门:基本能上手写Intrinsics
1.1 Neon介绍、简明案例与编程惯例
1.2 如何检索Intrinsics
1.3 优化效果案例
1.4 如何在Android应用Neon
2. 进阶:注意细节处理,学习常用算子的实现
2.1 与Neon相关的ARM体系结构
2.2 对非整数倍元素个数(leftovers)的处理技巧
2.3 算子源码学习(ncnn库,AI方向)
2.4 算子源码学习(Nvidia carotene库,图像处理方向 )
3. 学个通透:了解原理
3.1 SIMD加速原理
3.2 了解硬件决定的速度极限:Software Optimization Guide
3.3 反汇编分析生成代码质量
4. 其他:相关的研讨会视频、库、文档等

ncnn是腾讯开源,nihui维护的AI推理引擎。由于Neon实现往往跟循环展开等技巧一起使用,代码往往比较长。可以先阅读普通实现的代码实现了解顶层逻辑,再阅读Neon实现的代码。例如,我们希望学习全连接层(innerproduct)的Neon实现,其普通实现的位置在ncnn/src/layer/innerproduct.cpp,对应的Neon加速实现的位置在ncnn/src/layer/arm/innerproduct_arm.cpp。

 

2. ARMv8 中的 SIMD 运算

示例

4x4 矩阵乘法

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/time.h>

#if __aarch64__
#include <arm_neon.h>
#endif

static void dump(uint16_t **x)
{
    int i, j;
    uint16_t *xx = (uint16_t *)x;

    printf("%s:\n", __func__);

    for(i = 0; i < 4; i++) {
        for(j = 0; j < 4; j++) {
            printf("%3d ", *(xx + (i << 2) + j));
        }

        printf("\n");
    }
}

static void matrix_mul_c(uint16_t aa[][4], uint16_t bb[][4], uint16_t cc[][4])
{
    int i = 0, j = 0;

    printf("===> func: %s, line: %d\n", __func__, __LINE__);

    for(i = 0; i < 4; i++) {
        for(j = 0; j < 4; j++) {
            cc[i][j] = aa[i][j] * bb[i][j];
        }
    }

}

#if __aarch64__
static void matrix_mul_neon(uint16_t **aa, uint16_t **bb, uint16_t **cc)
{
    printf("===> func: %s, line: %d\n", __func__, __LINE__);
#if 1
    uint16_t (*a)[4] = (uint16_t (*)[4])aa;
    uint16_t (*b)[4] = (uint16_t (*)[4])bb;
    uint16_t (*c)[4] = (uint16_t (*)[4])cc;

    printf("aaaaaaaa\n");
    asm("nop");
    asm("nop");
    asm("nop");
    asm("nop");
    uint16x4_t _cc0;
    uint16x4_t _cc1;
    uint16x4_t _cc2;
    uint16x4_t _cc3;

    uint16x4_t _aa0 = vld1_u16((uint16_t*)a[0]);
    uint16x4_t _aa1 = vld1_u16((uint16_t*)a[1]);
    uint16x4_t _aa2 = vld1_u16((uint16_t*)a[2]);
    uint16x4_t _aa3 = vld1_u16((uint16_t*)a[3]);

    uint16x4_t _bb0 = vld1_u16((uint16_t*)b[0]);
    uint16x4_t _bb1 = vld1_u16((uint16_t*)b[1]);
    uint16x4_t _bb2 = vld1_u16((uint16_t*)b[2]);
    uint16x4_t _bb3 = vld1_u16((uint16_t*)b[3]);

    _cc0 = vmul_u16(_aa0, _bb0);
    _cc1 = vmul_u16(_aa1, _bb1);
    _cc2 = vmul_u16(_aa2, _bb2);
    _cc3 = vmul_u16(_aa3, _bb3);

    vst1_u16((uint16_t*)c[0], _cc0);
    vst1_u16((uint16_t*)c[1], _cc1);
    vst1_u16((uint16_t*)c[2], _cc2);
    vst1_u16((uint16_t*)c[3], _cc3);
    asm("nop");
    asm("nop");
    asm("nop");
    asm("nop");
#else
    printf("bbbbbbbb\n");
    int i = 0;
    uint16x4_t _aa[4], _bb[4], _cc[4];
    uint16_t *a = (uint16_t*)aa;
    uint16_t *b = (uint16_t*)bb;
    uint16_t *c = (uint16_t*)cc;

    for(i = 0; i < 4; i++) {
        _aa[i] = vld1_u16(a + (i << 2));
        _bb[i] = vld1_u16(b + (i << 2));
        _cc[i] = vmul_u16(_aa[i], _bb[i]);
        vst1_u16(c + (i << 2), _cc[i]);
    }

#endif
}

static void matrix_mul_asm(uint16_t **aa, uint16_t **bb, uint16_t **cc)
{
    printf("===> func: %s, line: %d\n", __func__, __LINE__);

    uint16_t *a = (uint16_t*)aa;
    uint16_t *b = (uint16_t*)bb;
    uint16_t *c = (uint16_t*)cc;

#if 0
    asm volatile(
        "ldr d3, [%0, #0]           \n\t"
        "ldr d2, [%0, #8]           \n\t"
        "ldr d1, [%0, #16]          \n\t"
        "ldr d0, [%0, #24]          \n\t"

        "ldr d7, [%1, #0]           \n\t"
        "ldr d6, [%1, #8]           \n\t"
        "ldr d5, [%1, #16]          \n\t"
        "ldr d4, [%1, #24]          \n\t"

        "mul v3.4h, v3.4h, v7.4h    \n\t"
        "mul v2.4h, v2.4h, v6.4h    \n\t"
        "mul v1.4h, v1.4h, v5.4h    \n\t"
        "mul v0.4h, v0.4h, v4.4h    \n\t"

        //"add v3.4h, v3.4h, v7.4h    \n\t"
        //"add v2.4h, v2.4h, v6.4h    \n\t"
        //"add v1.4h, v1.4h, v5.4h    \n\t"
        //"add v0.4h, v0.4h, v4.4h    \n\t"

        "str d3, [%2,#0]            \n\t"
        "str d2, [%2,#8]            \n\t"
        "str d1, [%2,#16]           \n\t"
        "str d0, [%2,#24]           \n\t"

        : "+r"(a),   //%0
          "+r"(b),   //%1
          "+r"(c)    //%2
        :
        : "cc", "memory", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7"
    );
#else
    // test, OK
    asm("nop");
    asm("nop");
    asm("nop");
    asm("nop");
    asm("nop");
    asm volatile(
        //"ld4 {v0.4h, v1.4h, v2.4h, v3.4h}, [%0] \n\t"
        "ld4 {v0.4h-v3.4h}, [%0]                \n\t"
        "ld4 {v4.4h, v5.4h, v6.4h, v7.4h}, [%1] \n\t"

        "mul v3.4h, v3.4h, v7.4h                \n\t"
        "mul v2.4h, v2.4h, v6.4h                \n\t"
        "mul v1.4h, v1.4h, v5.4h                \n\t"
        "mul v0.4h, v0.4h, v4.4h                \n\t"

        "st4 {v0.4h, v1.4h, v2.4h, v3.4h}, [%2] \n\t"

        : "+r"(a),   //%0
          "+r"(b),   //%1
          "+r"(c)    //%2
        :
        : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7"
    );
    asm("nop");
    asm("nop");
    asm("nop");
    asm("nop");
    asm("nop");
#endif
}
#endif

int main(int argc, const char *argv[])
{
    uint16_t aa[4][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {3, 6, 8, 1},
        {2, 6, 7, 1}
    };

    uint16_t bb[4][4] = {
        {1, 3, 5, 7},
        {2, 4, 6, 8},
        {2, 5, 7, 9},
        {5, 2, 7, 1}
    };

    uint16_t cc[4][4] = {0};
    int i, j;
    struct timeval tv;
    long long start_us = 0, end_us = 0;

    dump((uint16_t **)aa);
    dump((uint16_t **)bb);
    dump((uint16_t **)cc);

    /* ******** C **********/
    gettimeofday(&tv, NULL);
    start_us = tv.tv_sec + tv.tv_usec;

    matrix_mul_c(aa, bb, cc);

    gettimeofday(&tv, NULL);
    end_us = tv.tv_sec + tv.tv_usec;
    printf("aa[][]*bb[][] C time %lld us\n", end_us - start_us);
    dump((uint16_t **)cc);

#if __aarch64__
    /* ******** NEON **********/
    memset(cc, 0, sizeof(uint16_t) * 4 * 4);
    gettimeofday(&tv, NULL);
    start_us = tv.tv_sec + tv.tv_usec;

    matrix_mul_neon((uint16_t **)aa, (uint16_t **)bb, (uint16_t **)cc);

    gettimeofday(&tv, NULL);
    end_us = tv.tv_sec + tv.tv_usec;
    printf("aa[][]*bb[][] neon time %lld us\n", end_us - start_us);
    dump((uint16_t **)cc);

    /* ******** asm **********/
    memset(cc, 0, sizeof(uint16_t) * 4 * 4);
    gettimeofday(&tv, NULL);
    start_us = tv.tv_sec + tv.tv_usec;

    matrix_mul_asm((uint16_t **)aa, (uint16_t **)bb, (uint16_t **)cc);

    gettimeofday(&tv, NULL);
    end_us = tv.tv_sec + tv.tv_usec;
    printf("aa[][]*bb[][] asm time %lld us\n", end_us - start_us);
    dump((uint16_t **)cc);
#endif

    return 0;
}
1
aarch64-linux-gcc -O3  matrix_4x4_mul.c

gcc –march=armv8-a [input file] -o [output file]

3. NEON编程, 优化心得及内联汇编使用心得

Very thanks to Orchid (Orchid Blog).

 NEON intrinsics

提供了一个连接NEON操作的C函数接口,编译器会自动生成相关的NEON指令,支持ARMv7-A或ARMv8-A平台。

所有的intrinsics函数都在GNU官方说明文档

一个简单的例子:

//add for int array. assumed that count is multiple of 4  
#include<arm_neon.h>  
// C version void add_int_c(int* dst, int* src1, int* src2, int count)
{
  int i;
  for (i = 0; i < count; i++)
    dst[i] = src1[i] + src2[i];
  }
}
 
// NEON version void add_float_neon1(int* dst, int* src1, int* src2, int count)
{
  int i;
  for (i = 0; i < count; i += 4)
  {
    int32x4_t in1, in2, out;
    in1 = vld1q_s32(src1);
    src1 += 4;
    in2 = vld1q_s32(src2);
    src2 += 4;
    out = vaddq_s32(in1, in2);
    vst1q_s32(dst, out);
    dst += 4;
  }
}

代码中的vld1q_s32会被编译器转换成vld1.32 {d0, d1}, [r0]指令,同理vaddq_s32vst1q_s32被转换成vadd.i32 q0, q0, q0vst1.32 {d0, d1}, [r0]。若不清楚指令意义,请参见ARM® Compiler armasm User Guide - Chapter 12 NEON and VFP Instructions

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiangyong58

喝杯茶还能肝到天亮,共同进步

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值