嵌入式程序优化(2)——内嵌neon汇编
1. neon介绍
neon指令集是 arm 平台的 SIMD 指令集,也即单指令多数据指令集,如名字所说,一条只能可以同时处理多个数据,这里常常也使用另外一个名词来说 向量化编程。向量化编程在音视频处理领域中极为常见,随着人工智能深度学习等技术在嵌入式平台上的应用,neon指令集也可以被使用来优化某些后处理函数。本文将通过说明加代码例子讲解的方式尽量来阐述笔者的理解
PS:本文默认读者们已经熟悉了arm内嵌汇编语法
2. neon指令基础
本节将着重讲解一下 neon指令集 的基础知识,从而让读者更好地理解如何使用 neon指令
2.1 指令寄存器组
neon指令集 有专用的寄存器组,第一种是Q0-Q15一共16个,每个寄存器长度为128bit,第二种是D0-D31,一共32个,每个寄存器为64bit
neon寄存器有对照关系,可以理解为他们共同使用一块存储数据的区域,但该区域被分别映射为D寄存器和Q寄存器,下面的图是寄存器之间的对应关系
寄存器对应关系.pngneon指令集将每个寄存器视为均包含一个向量, 而该向量又包含 1、2、 4、 8 或 16 个大小和类型均相同的元素,也可以将各元素当作标量 加以访问。比如D0中有8个元素,每个元素是8bit,那么D0寄存器看成是有8个元素的向量
2.2 指令分类
neon指令 的操作对象为向量,向量有长有短,neon指令常用的有 64bit 的双字向量和 128bit 的四字向量其中,根据操作数的长度,可以将 neon指令 分为以下几种类型
-
通用指令集(Normal instructions):普通指令可以对任何向量类型进行操作,并产生与操作数向量相同大小且通常与类型相同的结果向量。即向量与结果长度相同
-
长整数指令集(Long instructions):长指令对 双字向量操作数 进行运算并产生 四字向量 结果。结果元素通常是操作数宽度的两倍,并且类型相同。即输入向量的长度为输出向量的一半(长指令使用附加在指令后的L来指定)。
-
宽整数指令集(Wide instructions):宽指令对 双字向量操作数 和 四字向量操作数 进行操作,产生 四字向量 结果 。结果元素和第一个操作数(四字向量操作数)的宽度是第二个操作数元素(双字向量操作数)的宽度的两倍。 即双字向量和四字向量计算后输出四字向量。宽指令在指令后附加了W。
-
窄整数指令集(Narrow instructions):窄指令对 四字向量操作数 进行运算,并产生 双字向量 结果。结果元素通常是操作数元素宽度的一半。即四字向量和四字向量计算后输出双字向量。窄指令通过在指令后附加N来指定。
2.3 指令格式
V{<mod>}<op>{<shape>}{<cond>}{.<dt>}{<dest>}, src1, src2
-
mod:即模式,neon指令集有几种计算模式,如下 Q: 饱和计算,各个数据类型的饱和范围请查看表. H: 该指令将结果减半。 它实际上通过向右移一个位来完成此操作,例如VHADD,VHSUB。 D: 使用指令使结果加倍 R: 对截断的指令结果进行舍入,比如某一个指令的操作数为整形,但指令结果带有小数,则需要对小数 进行舍入
-
op:执行的操作,比如加法ADD, 减法SUB, 乘法MUL).
-
shape:用于指定长指令(L), 宽指令(W), 窄指令(N),指的就是2.2节中的指令分类
-
cond:条件码, neon条件码与普通的arm汇编条件码使用的是同一套条件码,但其含义不同,详情查看手册
-
datatype:操作的数据类型,比如U8,U16等
-
dest:目的寄存器
-
src1:源寄存器1
-
src2:源寄存器2
PS:带花括号的字段为可选字段
2.4 指令编译
在编译含有neon指令集的代码时,是指定使用的fpu和cpu,通常情况下我们使用arm-gcc需要加-ftree-vectorize、-mfpu=neon、-mcpu=your_chip_arch 来使能编译器使用neon指令集
neon指令集常常是在C语言中使用,除了使用汇编代码来编写外,我们还可以使用neon的开源库如Ne10等,也可以使用官方提供的neon内建函数来
同时,gcc编译器 支持对一般的代码进行 neon优化,但需要满足以下条件
• 短而简单的循环
• 不使用break跳出循环.
• 循环次数为2的幂次.
• 循环次数为编译器支持的范围.
• 循环内部调试函数时,该函数需要是内联属性
• 使用数组索引而不是指针.
• 间接寻址无法向量化.
• 使用strict关键字告诉编译器指针不引用内存的重叠区域。
3. 例子说明
下面通过一个简单的 neon指令 的语句来了解一下具体的语法
指令示意.png上面的图例应该展现得足够清楚
qd 是mod字段,表明该指令支持饱和和对结果进行双倍放大操作
mla是neon指令集操作,标志相乘并累加结果
l 表示的是指令的操作类型为长整型操作
s16 表示的是向量中的元素长度
dest 表示的是目标寄存器
src 表示的是源寄存器
关于 neon 的具体例子请参加笔者的github,地址为https://github.com/wipping/neon
因为该代码例程中有上千行,不便在文章中呈现,请读者们下载阅读,代码中做了相应的注释,当然还是要搭配neon指导手册 进行阅读。因为代码是根据手册中的内容进行编写
4. 参考资料
NEON简介及基本架构:http://zyddora.github.io/2016/02/28/neon_1/
ARM平台NEON指令的编译和优化https://blog.csdn.net/heli200482128/article/details/79303286
neon函数速查地址: https://developer.arm.com/architectures/instruction-sets/simd-isas/neon/intrinsics