ARMv7-A 下的 Neon 指令
数据类型
- 32 bits 单精度浮点数
- 8, 16, 32, 64 bits 无符号有符号整形
- 8, 16 bits 多项式
类型说明符
- 无符号数
U8,U16,U32,U64 - 有符号数
S8,S16,S32,S64 - 无指定类型整型
I8,I16,I32,I63 - 浮点数
F16,F32 - 0 - 1 多项式
P8
指令格式
Neon 指令的一般格式如下
1 | V{<mod>}<op>{<shape>}{<cond>}{.<datatype>} {dest}, <src1>, <src2> |
- mod
- Q
指令使用饱和算法,所以结果总会在指定的数据类型的表示范围之间 - H
指令会使结果减半,将结果右移一位实现 - D
指令会使结果乘二 - R
指令会将结果取整,等价于加上 0.5 之后截断小数部分
- Q
- op
操作,如ADD,SUB - shape
- L
操作双字 vector, 生成四倍长字 vector
结果宽度通常是操作数的两倍,类型相同 - W
操作双字 + 四倍长字,结果和第一个操作数都是第二个操作数宽度的两倍 - N
操作四倍长字,结果生成双字,结果宽度一般是操作数的一半。
- L
- cond
与 IT 指令混用 - datatype
数据类型,s8, u8, f32 - dest
目的寄存器 - src1
第一操作数 - src2
第二操作数
饱和算法
- ARM 中的饱和算法
- 对于有符号饱和运算,若结果小于
, 那么返回结果 - 对于无符号饱和运算,若结果小于 0, 那么结果返回 0, 如果结果大于
, 那么返回
- 对于有符号饱和运算,若结果小于
- Neon 中的饱和算法
指令中使用 Q 前缀指定饱和算法,原理与 ARM 相同。
GCC 内联汇编
发表于
更新于
语法格式
1 | asm volatile |
asm volatile
表示后面的代码为内嵌汇编,asm是__asm__的别名。volatile表示编译器不能优化代码。- 汇编语句模板
由汇编语句序列构成,语句之间用;,\n,\n\t分隔. 指令中的操作数可以用占位符引用 C 语言变量. 指令中使用占位符表示的操作数总被视为long型(4 个字节)。使用的操作符可以作用与字或字节,默认作用于低字节。可以用h或b修饰,如%h1. - 输出部分
格式为"=?"(var)的形式,var可以是任意内存变量(输出结果会存到这个变量中),?一般是下面这些标识符。- a, b, c, d, S, D 分别代表
eax,ebx,ecx,edx,esi,edi,寄存器 r代表上面这些寄存器中任意一个(哪个闲置用哪个)m内存i立即数(常量,只用于输入操作数)g, 寄存器,内存,立即数(由编译器决定)
在汇编中,用%序号代表这些输入/输出操作数,序号从 0 开始。为了与操作数分离,寄存器用标出, 如%%eax.
- a, b, c, d, S, D 分别代表
- 输入操作数
格式为?(var),?除了可以是上面这些标识符,还可以是输出操作数的序号,表示用var初始化该输出操作数。 - 破坏描述部分
在汇编代码中修改,又没有在输入/输出列表中列出的寄存器,这样 gcc 就不会擅自使用这些寄存器,用memory表示在内联汇编中修改了内存,之前缓存在寄存器中的内存变量需要重新读取。
示例
1 | #include <stdio.h> |
Winograd Small Convolution Algorithm
Winograd 快速卷积算法
ARM Neon 常用指令
记下编程中常用的指令
Arm Neon 编程(三):运算指令
VADD, VSUB
ARM Neon 编程(二) 访存指令详解
VLD, VST
ARM Neon 编程(一):读取与存储
Neon 中的 load 与 store 指令
ARM Neon 指令入门
Neon 基础
Neon 是适用于 ARM 系列处理器的一种 SIMD(Single Instruction Multiple Data)扩展结构。Neon 有自己的执行管道和寄存器组。也就是说 Neon 和 ARM 指令是各自独立执行的。 Neon 的寄存器有
- 32 个 64 位的寄存器(D0-D31)
- 16 个 128 位的寄存器(Q0-Q15), ARM64 中有 32 个 128 位的寄存器
实际上 D 寄存器和 Q 寄存器是重叠的。如下图所示。两个 D 寄存器对应一个 Q 寄存器。

在 ARM64 中,虽然 Q 寄存器有 32 个,但是 D 寄存器也只有 32 个,也就是说 Q0 只和 D0 重叠,Q1 只和 D1 重叠。
Neon 的作用在于使用向量化技术加速计算。
关键概念
- 数据类型(data type)
Neon 寄存器能存放大部分的基本数据类型,如int8,int16。一个寄存器能存储的数据个数与数据大小和寄存器大小有关。 - 向量(vector)
在 Neon 中一个寄存器可以看做一个向量。如 64bits 的寄存器 D0,存储 4 个int16元素,就可以看做 length 为 4 的向量。 - 管道(lang)
在上面 D0 存储 4 个int16, 那么 D0 就有四个通道, pipe0 到 pipe3, pipe0 在 D0 的低 bit 位.
使用方法
intrinsics(内部函数)
使用 intrinsics 不如使用内联汇编效率高。但是使用 intrinsics 较为简单且易于维护。这些函数在编译时会直接转化为 Neon 的汇编指令
1 | #include <arm_neon.h> |
使用开源库
基于 Neon 的开源库如 Project Ne10, OpenMAX DL.
内联汇编
直接在 C/C++ 代码中内联汇编,但是可移植性差,且难度较高
编译器产生向量化指令
可以通过添加一些编译选项的方法使能向量化编译,但是对于复杂算法编译器的效果较差
NEON 内部函数
数据类型
基本数据类型
- 64 位
int8x8_t,int16x4_t,int32x2_t,int64x1_t,
uint8x8_t,uint16x4_t,uint32x2_t,uint64x1_t,
float32x2_t,float64x1_t(少见) - 128 位
int8x16_t,int16x8_t,int32x4_t,int64_2_t,
uint8x16_t,uint16_8_t,uint32x4_t,uint64x4_t,
float32x4_t
结构化数据
差不多每种基本数据类型都有其对应的结构化数据类型
int8x8x2_t,int16x4x2_t,uint8x16x2_t,uint16x8x2_t
int8x8x3_t,int16x4x3_t,uint8x16x3_t,uint16x8x3_t
int8x8x4_t,int16x4x4_t,uint8x16x4_t,uint16x8x4_t
函数分类
Neon 内部函数分为以下几类,使用时要包含 arm_neon.h 头文件
- 初始化寄存器
- 从内存加载数据到 neon 寄存器
- 将 Neon 寄存器数据存储到内存
- 直接从 Neon 寄存器获取某个通道的值
- 直接设置 Neon 寄存器某个通道的值
- 寄存器数据重排
- 加减法
- 乘法
- 乘加组合运算
- 乘减组合运算
- 取整
- 比较运算
- 绝对值
- 取最大最小值
- 取倒数
- 平方根倒数
- 移位运算
- 取负数
- 按位运算
- 统计
- 数据类型转换
实例代码
1 | void first_example_c(unsigned char *src, unsigned char *dst, int size, unsigned char factor) |