美文网首页
嵌入式程序优化(2)——内嵌neon汇编

嵌入式程序优化(2)——内嵌neon汇编

作者: wipping的技术小栈 | 来源:发表于2019-11-10 17:35 被阅读0次

    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寄存器,下面的图是寄存器之间的对应关系

    寄存器对应关系.png

    neon指令集将每个寄存器视为均包含一个向量, 而该向量又包含 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

    相关文章

      网友评论

          本文标题:嵌入式程序优化(2)——内嵌neon汇编

          本文链接:https://www.haomeiwen.com/subject/huuabctx.html