原文:https://blog.csdn.net/sunty2016/article/details/79857825

本文打算介绍下ARM的SIMD指令在C语言下intrinsics函数的使用方法,算是对于NEON的一个入门吧。严格来说本文并不是关于ARM汇编的,但是多多少少有关系。
SIMD

什么是SIMD呢?就是一条指令处理多个数据,可以算作是一种并行计算。比如我们要做一个4维向量的加法,用一般的指令完成必须使用4次加法指令才行,而用SIMD指令可能只需要一次加法,而且花费的时间和一般指令做一次加法的时间相同。很显然,SIMD可以大大提高一些计算密集型任务的执行效率。这种SIMD指令功能,主流的体系结构一般都用一组特殊的指令子集给予支持,比如x86的SSE,还比如本文讲的ARM的NEON。
NEON

NEON是ARM下的一个SIMD指令集合。可实现64位/128位的并行计算。64位/128位并行怎么理解呢?举例说,在128位并行的情况下,如果是8位整数,可以并行进行16对整数的加法;如果是16位整数,就可以并行进行8对整数的加法;以此类推。

指令集合自然也离不开寄存器。NEON寄存器分两种。一种寄存器以D开头,共32个,每个64位;另一种寄存器以Q开头,共16个,每个128位。Q0与D0,D1重合(共用128比特),Q1与D2,D3重合,以此类推。因此用D寄存器可并行8个8位整数加法,而用Q寄存器可并行16个8位整数加法。
NEON intrinsics

如果直接用汇编写NEON固然可以,但是coding的效率不会很高。C编译器支持将NEON指令封装成内置函数供程序员直接使用,这样一来无疑会大大提高开发效率和代码可维护性。

同时,执行效率也并不会降低很多,因为使用NEON intrinsics时,虽然像是在调用各种结构体和函数,但将生成的代码反汇编后可以发现,其实没有调用函数,只是在使用NEON寄存器和指令罢了。

即便目的是写汇编代码,使用intrinsics也有好处。比如先用intrinsics写好代码编译后在反汇编,在此基础上进行优化,可能比较省力。
数据类型

<基本类型>x<lane个数>x<向量个数>_t,向量个数如果省略表示只有一个。如int8x8_t,uint8x8x3_t。

基本类型int8,int16,int32,int64,uint8,uint16,uint32,uint64,float16,float32

lane个数表示并行处理的基本类型数据的个数。

对于多个向量的类型实际上是结构体

typedef struct {
        uint8x8_t val[3];
    } uint8x8x3_t;

指令命名

<指令名>[后缀]_<数据基本类型简写>

其中后缀如果没有,表示64位并行;如果后缀是q,表示128位并行。

如果后缀是l,表示长指令,输出数据的基本类型位数是输入的2倍;如果后缀是n,表示窄指令,输出数据的基本类型位数是输入的一半。

数据基本类型简写:s8,s16,s32,s64,u8,u16,u32,u64,f16,f32

例如:

vadd_u16:两个uint16x4相加为一个uint16x4

vaddq_u16:两个uint16x8相加为一个uint16x8

vaddl_u16:两个uint8x8相加为一个uint16x8
指令分类说明
算术和位运算指令

vadd,vsub,vmul,vand,vorr,vshl,vshr等。

但是NEON不直接提供除法和开平方指令,而是提供了对于倒数1/x和开方的倒数1/x^0.5的近似指令。这样一来除法a/b可以表示为a*(1/b),开方a^0.5可以表示为a*(1/a^0.5)。

示例:

//近似求倒数
    inline static float32x4_t vrecp(float32x4_t v) {
        float32x4_t r = vrecpeq_f32(v);        //求得初始估计值
        r = vmulq_f32(vrecpsq_f32(v, r), r);    //逼近
        r = vmulq_f32(vrecpsq_f32(v, r), r);    //再次逼近
        return r;
    }

//近似求开方
    inline float32x4_t vsqrt(float32x4_t v) {
        float32x4_t r = vrsqrteq_f32(v);        //求得开方倒数的初始估计值
        r = vmulq_f32(vrsqrtsq_f32(v, r), r);    //逼近
        return vmulq_f32(v, r);                //通过乘法转为开方
    }

数据移动指令

实际编程中经常要在不同NEON数据类型间转移数据,有时还要按lane来get/set向量值,NEON intrinsics也提供了这类操作。

vdup[后缀]_n_<数据基本类型简写>:用同一个标量值初始化一个向量全部的lane;

vset[后缀]_lane_<数据基本类型简写>:对指定的一个lane进行设置

vget[后缀]_lane_<数据基本类型简写>:获取指定的一个lane的值

vmov[后缀]_<数据基本类型简写>:数据间移动
访存指令

NEON访存指令可以将内存读到NEON数据类型中去,或者将NEON数据类型写进内存。可以支持一次读写多向量数据类型。

vld<向量数>[后缀]_<数据基本类型简写>:读内存

vst<向量数>[后缀]_<数据基本类型简写>:写内存

例如,vld1_u8从内存读取一个uint8x8_t数据,vst3q_u8写入一个u8x16x3_t数据。

需要注意的是,默认情况下对多个向量数据的读写使用了interleave模式,可以理解为向多向量数据读入或从其写出时外层按照lane循环,内层再按照向量循环。

例如将一个16像素的RGB图片解析成R,G,B三个plane的时候,可以写如下代码:

void split(uint8_t *rgb, uint8_t *r, uint8_t *g, uint8_t *b) {
        uint8x16x3_t v = vld3q_u8(rgb);
        vst1q_u8(r, v.val[0]);
        vst1q_u8(g, v.val[1]);
        vst1q_u8(b, v.val[2]);
    }

条件指令

如同非SIMD程序需要分支语句一样,NEON程序有时候需要对一个向量的各个lane的值的情况来判断另一个向量对应的lane如何进行处理。

vce[后缀]_<数据基本类型简写>:v[n] = v1[n] == v2[n] ? 全0 : 全1

vcle[后缀]_<数据基本类型简写>:v[n] = v1[n] <= v2[n] ? 全0 : 全1

vclt[后缀]_<数据基本类型简写>:v[n] = v1[n] < v2[n] ? 全0 : 全1

vcge[后缀]_<数据基本类型简写>:v[n] = v1[n] >= v2[n] ? 全0 : 全1

vcgt[后缀]_<数据基本类型简写>:v[n] = v1[n] > v2[n] ? 全0 : 全1

得出的结果结合位运算即可实现条件判断。
注意事项

NEON intrinsics的注意事项同时也是NEON汇编的注意事项。

处理数组时要注意数组元素个数不能被NEON向量lane个数整除的情况,多出的元素应补齐或者通过非SIMD方式处理。

NEON不是万能的,比如把地址放在向量里让内存同时读写就办不到。设计算法时应尽量避免这种情况。

对cache友好仍然是最重要的。有时一个算法看上去似乎访存次数和计算次数都比另一个算法少,但是由于其访存方式对cache不友好,导致其运行效率不如后者。

NEON intrinsics 函数模式介绍相关推荐

  1. 用 Neon Intrinsics 优化 C 代码

    以下内容翻译自:Optimizing C Code with Neon Intrinsics 概述 本指南向您展示如何在 C 或 C++ 代码中使用 Neon intrinsics 函数,以利用 Ar ...

  2. Neon intrinsics

    1.介绍 在上篇中,介绍了ARM的Neon,本篇主要介绍Neon intrinsics的函数用法,也就是assembly之前的用法.NEON指令是从Armv7架构开始引入的SIMD指令,其共有16个1 ...

  3. 【genius_platform软件平台开发】第四点:ARM NEON Intrinsics 使用详解

    目录 前言 SIMD简介 ARM NEON Intrinsics简介 函数改写示例 结语 前言 最近公司在视频直播项目中要使用H.265/HEVC,具体的是使用HW硬件编码H.264/AVC,云端转码 ...

  4. Neon intrinsics 简明教程

    文章目录 前言 SIMD & NEON NEON intrinsics NEON intrinsics 学习资料 寄存器 向量数据类型 NENO intrinsics 命名方式 NEON In ...

  5. 【neon加速拆分/合并交叉数据】使用neon intrinsics加速合并/拆分uv的内存分布(交叉存储/分别存储)

    说明 在YUV格式的图片中,uv数据可以"UVUVUVUV"形式交叉存储叫NV12,也可以以"UUUUUVVVVV"的格式分开存储,为了将二者转换,需要类似如下 ...

  6. 大前端CPU优化技术--NEON intrinsics进阶

    前言 今天我们继续介绍NEON intrinsics的指令知识,上篇大前端CPU优化技术--NEON intrinsics开篇中已经介绍了部分指令的作用.本篇文章除了介绍指令还会附上场景示例,方便大家 ...

  7. 【genius_platform软件平台开发】第八十二讲:ARM Neon指令集一(ARM NEON Intrinsics, SIMD运算, 优化心得)

    1. ARM Neon Intrinsics 编程 1.入门:基本能上手写Intrinsics 1.1 Neon介绍.简明案例与编程惯例 1.2 如何检索Intrinsics 1.3 优化效果案例 1 ...

  8. ARM Neon Intrinsics 学习指北:从入门、进阶到学个通透

    本文同步发表于GiantPandaCV公众号,未经作者允许严禁转载 前言 Neon是ARM平台的向量化计算指令集,通过一条指令完成多个数据的运算达到加速的目的,常用于AI.多媒体等计算密集型任务. 本 ...

  9. NEON Intrinsics 练习题

    系列文章目录 数字信号处理中的 SIMD Neon intrinsics 简明教程 用 NEON 实现高效的 FIR 滤波器 前言 关于 SIMD,或者说 NEON,我已经发布了几篇文章来介绍它了,如 ...

最新文章

  1. Linux实现ffmpeg H.265视频编码
  2. 第二期安全狗讲坛3月11日开始
  3. CUDA学习(十五)
  4. 划动浮空岛_划动浮空岛攻略轻松通关要点详解
  5. Node-Web应用框架Express
  6. Eclipse中要导出jar包中引用了第三方jar包怎么办
  7. oracle添加伪列,Oracle伪列 - jifengtang的个人空间 - OSCHINA - 中文开源技术交流社区...
  8. java jvm内存地址_JVM--Java内存区域
  9. 信息安全工程师笔记-网络安全测评技术与标准
  10. linux 存储映射lun 给_linux 存储多路径聚合和映射
  11. ping不同的网卡方法
  12. 【ICLR2020】通过强化学习和稀疏奖励进行模仿学习
  13. Java入门-换行输出
  14. 怎么用python下载视频_用python一行代码批量下载哔哩哔哩视频
  15. [zkaq靶场]FineCMS的存储型XSS
  16. ORA-30926: unable to get a stable set of rows in the source tables
  17. Nginx - 静态网站;负载均衡;静态代理;动静分离;虚拟主机
  18. C语言中getchar()函数的用法
  19. 翟天临之后,大连博士细数区块链博士圈那些事……
  20. Java使用poi做加自定义注解实现对象与Excel相互转换

热门文章

  1. 小米手机计算机软件,手机计算器
  2. 如何用蓝牙网关广播蓝牙数据
  3. 太空垃圾有解决办法?RemoveDEBRIS 卫星将使用鱼叉设备带回垃圾
  4. AVS2解码图像管理
  5. 【小程序】微信小程序自定义导航栏及其封装
  6. 苹果iPhone14频繁自动重启是什么原因?如何修复这个问题?
  7. Windows10系统下电脑时间不对,怎么办?
  8. 【C语言编程4】输入年份输出全年日历
  9. 用python做一个木马_Python编程简单的木马程序(转载于乌云中)
  10. 申请邓白氏D-U-N-S编码遇到两个公司名称一样英文翻译的解决方法