一、概述neon是arm上使用的一种simd(single instruction multiple data – 单指令多数据)指令集。可实现64位/128位的并行计算。简单理解就是一个计算指令,可以指定4个float和4个float并行计算(也可以是其他数据类型,但是必须包含在64位/128位内),得到4个float结果。而不是一次只能一个float和一个float的计算。
比如在rgb颜色转灰色时,计算公式为:gray = r *0.299 + g *0.587 + b * 0.114,计算过程是由3个float乘法,2个加发组成,共有5个计算指令;如果直接使用neon指令,就是可以直接通过一个指令计算完成,提升80%的理论性能。
矩阵计算就更为明显,在4x4的矩阵和4个元素的向量相乘时,有16个float乘法和12个加法计算;neon可以4个指令直接计算,提升的性能更明显。
当然,这种计算需要是一种矩阵或者像素计算密集型的场景,比如rgb图片转黑白色,不通过gpu加速,而是通过cpu计算的场景;有多个3d模型,每帧需要为每个3d模型进行矩阵计算等等。
二、neon在矩阵&向量中的计算示例向量的点积运算示例(这里向量以4个元素为例,前3个元素通常表示3d空间的xyz坐标,第4个元素w用于齐次坐标;也可以表示颜色的rgba)。两个向量分别是:,,向量的点积计算公式:。对应的neon加速代码如下:
类似vdupq_n_f32、vld1q_f32、vmlaq_f32、vadd_f32、vget_lane_f32等等apis,都是arm neon的intrinsics指令,c格式的api。并且这些apis都定义在arm_neon.h头文件中。arm neon指令有两种实现方式,一种就是示例中的intrinsics指令,另外一种就是直接使用neon的汇编指令,嵌入到c语言代码中。我们这里只是以intrinsics指令为例,汇编指令在原理上一样。
三、示例代码中apis的说明3.1 arm neon向量寄存器
向量寄存器用来存放向量数据,每个向量元素的类型必须相同。这个向量寄存器有128位,aarch64有32个这个寄存器,aarch32/armv7有16个这个寄存器。
每个寄存器可以表示2个double float类型数据(每个数据占用64位),4个float类型数据(每个数据占用32位),8个short类型数据(每个数据占用16位),16个byte类型数据(每个数据占用8位)。数据类型可以是整形,也可以是浮点数,只要占用位数对齐,类型统一即可。
3.2 示例说明
在计算时,第一步是要把c代码中定义的数据(数组的形式存在,在运行栈中,或者在堆中)加载到向量寄存器中,第二步通过寄存器进行并行计算,第三步把结果写入到指定寄存器,第四步寄存器结果写入c代码对应的变量中(即c语言的栈或者堆中)。
第一步:vld1q_f32的意思就是把” a + k”地址指向的内容加载到向量寄存器。f32的意思是,一个值是32位。这个命令是从指定地址,连续复制数据到寄存器,并填满寄存器。比如,这里一个数据是32位,一个寄存器128位,也就是这个命令会连续填充4个f32值。说明:这里是多对(“k”个)向量进行点积计算。
第二步:vmlaq_f32意思是把两个寄存器中,并行4个通道的4个f32分别对应相乘,同时把结果和保存结果的寄存器对应通道进行累加。
第三步:vget_high_f32、vget_low_f32是取寄存器的高位和低位(按照f32的type,分别有2个通道),vadd_f32就是获取高位2通道和低位2通道分别相加,存到一个float32x2_t数据格式用(f32类型,2通道)。vpadd_f32中的p是pairwise,意思是将参数两个向量的相邻数据进行计算,这里就是r自己的2个相邻通道相加。
第四步:vget_lane_f32比较简单,就是获取第一个参数寄存器中指定通道的值。这里就是第0通道的值。并写会到一个float值中。
四、点积的推广这里的点积相对比较复杂,考虑到了一些通用性。这里使用了一个for循环,当只是计算两个4元素向量的点积时,可以把for循环去掉,vmlaq_f32由vmull_f32替换即可。vmull_f32的原型:result_t vmull_type(vector_t n, vector_t m),result_t可以是float32x4_t,m和n就是left_vec和right_vec。
如果进行叉乘,则不需要进行第三步,直接返回一个float32x4_t的类型数据即可。
如果计算矩阵(4x4)和向量(4通道)相乘,就是计算点积4次,并且结果分别放到float32x4_t类型的4个通道中。
如果是矩阵(4x4)相乘则是4个叉乘。
这四种情况可以自己根据上方点积的计算方式,独立写出。
五、数据类型和函数指令说明其实neon intrinsics指令中,对使用的变量类型、函数定义做了扩展,便于记忆和理解。
1.比如下方的数据类型:
a. int是数据类型,可以是int/uint/float/poly等等。
b. 后边几个数字由‘x’号链接,第一个数字就是每个元素的大小,这里是bit,而非byte,可以是8/16/32/64。
c. 第二个数字是通道。比如表示颜色的rgba,就是4通道,每个通道可以用一个byte表示(这里其实就是int8类型)。表示3d空间坐标,可以是xyz,就是3通道。如果是一个2d平面,就是一个xy,2通道了。
d. 最后一个数字表示有多少个。比如一个3d空间坐标xyz,一个四边形有4个顶点,这里就可以表示4(这个值通常是一个2的次幂数)。
这里可以根据实际情况选择自己的数据类型。不过要注意,这里要和128位对齐,符合自己实际数据对齐逻辑,不能超出。
2.函数也有类似的表达方式,例如:
v表示的aarch32/armv7的指令
p表示pairwise计算。这里表示的是a和b向量的相邻数据进行两两和操作,如下方的操作方式:
add就是加法,加减乘除普通计算,还有一些操作,比如加载、存储、移位、逻辑计算、类型转换等等。
q表示试用128位的向量计算器,不然就使用64位向量寄存器。
s8就是数类型了,可以是:u8、s8、u16、s16、u32、s32、f32、f64。
更多的内容可以在底部参考资料中,找到相关内容。
通过数据类型和函数类型,我们就可以根据实际情况,结合这些函数,封装我们自己的加速代码逻辑,达到优化的目的。
六、总结
这里只是对点积计算方式进行了解析,同时对于其他情况的推广。其实对于int、char等类型可以类比计算。对像素、向量、矩阵等等的计算会成倍提升(理论性能提升16、8、4、2倍不等,根据实际类型确定)。特别是在移动端,图形计算、图形处理领域,cpu性能遇到瓶颈,进行性能优化时,neon指令是一个不错的优化点。
《炬丰科技-半导体工艺》 HQ2和HF溶液循环处理
区块链技术的运用
便携医疗初显峥嵘 MTK风生水起
Mozilla 结束 Firefox 火狐浏览器扩展推广计划
IGBT模块关断电阻对关断尖峰的非单调性影响
ARM NEON在矩阵&向量计算中的加速概述
新品强势来袭!三星智慕·多维双驱系列引领智能健康洗护生活
还在追i7吗?英特尔酷睿i9处理器曝光!四个型号最快8月发布
如何快速准确地读出色环电阻的值
水质检测常用这几款传感器!
iPhone8:OLED屏幕是板上钉钉,售价7000元起步!
健身房新推出的智能硬件,智能镜子已强势来袭
翼联EDUP 1080P全高清CMOS自动感光摄像头好用吗
未来几年间全面屏iPhone的发展趋势
芯启源首次公布“SmartNICs第四代架构”
2018年全球前25大半导体厂商的总营收增加16.3%
AI会将艺术家代替掉吗
5G对定位技术有何影响?
一文告诉你如何驱动MOS管
示波器DSO9104A的安全操作有哪些