Neon优化第二章 一个简单地Neon实例

本文链接

什么是Neon内建函数(Neon intrinsics)

  内建函数是编译自己“知道的函数”,因此都是可以直接调用。Neon内建函数是一系列c/c++函数的集合,这些函数定义在"arm_neon.h"中,arm和gcc编译器都支持。这些函数可以理解为是一个个kernel函数或者inline函数,内部实现是使用Neon指令写的,在编译的时候,会直接在预处理阶段将inline的代码"粘贴"上去,同时pipline优化这些工作都可以交给编译器去完成,而不用工程师自己手动完成。
  之前看了很多的资料都是介绍Neon,但很少有人提及怎么用的。比如我一开始用Neon的时候,知道它的原理是啥,但怎么用呢?需要链接特殊的库吗,用特殊的编译器吗,编译器需要特殊的参数吗?这些都未提及,当然可以理解的是,写Neon资料的朋友,很多可能默认读者已经对编译知识很理解,是老手了。我自己比较笨,所以这里提醒一下各位新手朋友,Neon内建函数的使用很简单,在代码中include头文件,直接使用,编译的时候不需要任何特殊的命令,后面我会给一个编译脚本,看脚本就一目了然了

  Neon内建函数可以参考Neon intrinsics Reference 使用内建函数有以下好处;

  1. 很强大: Neon 内建函数可以让开发者直接使用Neon指令,而不用手动编写。
  2. 兼容性强: Neon内建函数可以兼容不同的平台,如果是手动编写的Neon指令,可能需要根据不同平台去重写
  3. 灵活性: 开发者可以在需要的时候使用Neon内建函数,在不需要的时候需要c/c++。

  Neon内建函数也有缺点:

  1. 性能比不上手写的Neon指令代码
  2. 相比直接使用Neon的三方库,Neon内建函数的学习曲线较为陡峭。

Neon的一个例子: RGB通道分离

在做计算机视觉的时候,使用的opencv读取图片,在内存上是按照BGR顺序排列的,如下图所示:
在这里插入图片描述
[该图原地址]:(https://docs.opencv.org/4.x/tutorial_how_matrix_stored_2.png)

  关于opencv内存更多的资料请参考opencv如何扫描一副图片
  但在实际的而开发工作中,有的时候需要将BGR三个通道分开,分别进行处理。我们常用的代码可能是这样的:

void rgb_deinterleave_c(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *rgb, int len_color) {
    /*
     * Take the elements of "rgb" and store the individual colors "r", "g", and "b".
     */
    for (int i=0; i < len_color; i++) {
        r[i] = rgb[3*i];
        g[i] = rgb[3*i+1];
        b[i] = rgb[3*i+2];
    }
}

  但是有一个问题,在优化级别 -O3(非常高的优化)下使用 Arm Compiler 6 进行编译并检查反汇编显示没有使用 Neon 指令或寄存器,每个单独的 8 位值存储在单独的 64 位通用寄存器中。考虑到全宽 Neon 寄存器为 128 位宽,在示例中每个寄存器可以保存 16个8位值,使用内建函数的rgb通道分离函数如下(opencv是bgr排布, 这里代码里写的是rgb, 并不影响理解)。

void rgb_deinterleave_neon(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *rgb, int len_color) {
    /*
     * Take the elements of "rgb" and store the individual colors "r", "g", and "b"
     */
    int num8x16 = len_color / 16;
    uint8x16x3_t intlv_rgb;
    for (int i=0; i < num8x16; i++) {
        intlv_rgb = vld3q_u8(rgb+3*16*i);
        vst1q_u8(r+16*i, intlv_rgb.val[0]);
        vst1q_u8(g+16*i, intlv_rgb.val[1]);
        vst1q_u8(b+16*i, intlv_rgb.val[2]);
    }
}

在这个例子中,我们使用了以下的Neon内建函数:

内建函数含义作用
uint8x16_t16个8位无符号整数的数组一个uint8x16_t的类型可以填满整个128位的Neon寄存器,保障没有浪费资源
uint8x16x3_t包含三个uint8x16类型的结构体循环中当前像素RGB三通道值得临时存储位置
vld3q_u8(…)通过加载 3*16 字节内存的连续区域返回 uint8x16x3_t 的函数。加载的每个字节都以交替模式放置在三个 uint8x16_t 数组之一中。生成 LD3 指令,该指令将给定地址中的值以交替模式加载到三个 Neon 寄存器中。具体作用可以参考下图
vst1q_u8(…)在给定地址存储 uint8x16_t 的函数。存储整个128位数据的函数

vld3q_u8(…)函数示意图
​   ​   ​    vld3q_u8(…)函数示意图

​   上面的代码可以通过下面的编译命令和反汇编查看:

gcc -g -o3 rgb.c -o exe_rgb_o3
objdump -d exe_rgb_o3 > disasm_rgb_o3

   我自己写了一个完整的demo进行测试,在打开o3的情况下,前后对比如下:

打开o3

   相差7倍左右,如果不开O3的情况下,两者相差有将近25倍:

未打开o3  关于开了O3对两个函数做的优化,将以后分别讨论,这里不展开讨论。代码链接

  本文是一个系列的文章,目前已经发布在我个人的公众号和知乎上面
公众号: kkchen, Neon优化第二章 一个简单地Neon实例
[知乎文章](Neon优化第二章 一个简单地Neon实例)
欢迎大家关注,如果有问题讨论可以私信我。kkchen就是我,我就是kkchen。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值