VIO在ARM上的加速(2)- Neon

news2024/11/27 0:35:36

VIO在ARM上的加速:

VIO在ARM上的加速(1)- ARM加速基础

VIO在ARM上的加速(2)- Neon

VIO在ARM上的加速(3)- Neon在VIO中的应用

1 NEON的概述

ARM 处理器中使用的高级 SIMD 扩展的实现称为 NEON,这是架构规范之外使用的常用术语。

NEON指令只适用于支持NEON的系统。

ARMv7-M不支持NEON,NEON技术只适用于ARM Cortex-A系列处理器。

NEON的指令集只是ARM和THUMB指令集中的子集。

NEON的指令都是以V字母开头。

使用intrinsics(内联函数)不如使用汇编优化效率,这些函数在编译的时候会直接转化成NEON的汇编指令,使用intrinsics没法控制寄存器分配和内存对齐等。

为了支持这些内联函数必须要包含头文件arm_neon.h , 使用NEON技术还要通过在编译的时候加入-mfpu=neon才能起到效果。

NEON注意事项:

(1)、load数据的时候,第一次load会把数据放在cache里面,只要不超过cache的大小,下一次load同样数据的时候,则会比第一次load要快很多,会直接从cache中load数据;

(2)、在做NEON乘法指令的时候会有大约2个clock的阻塞时间,如果你要立即使用乘法的结果,则就会阻塞在这里。乘法的结果不能立即使用,可以将一些其它的操作插入到乘法后面而不会有时间的消耗;

(3)、使用饱和指令的时候,如乘法饱和的时候,在做乘法后会再去做一次饱和,所用时间要比直接做乘法要慢;

(4)、在对16位数据进行load或者store操作的时候,需要注意的是字节移位。

2 NEON数据类型与指令

NEON中的数据类型说明符由一个指示数据类型的字母构成,该字母通常后跟一个指示宽度的数字。

在ARMv7和ARMv8上,NEON支持的数据类型几乎相同,但是ARMv8引入了更多的数据类型支持。
在ARMv7上,NEON支持的数据类型包括:
整数:8位、16位、32位和64位整数
浮点数:32位和64位浮点数
在ARMv8上,NEON支持的数据类型包括:
整数:8位、16位、32位和64位整数,以及128位长整数
浮点数:16位、32位和64位浮点数,以及128位长浮点数
​所以,虽然在ARMv7和ARMv8上NEON支持的数据类型几乎相同,但ARMv8引入了更多的数据类型支持。
数据类型针对的是操作数,而不是目标数(结果)。

在ARMv7上,VCVT指令在单精度浮点和以下元素之间进行转换:32 位整数,定点,半精度浮点(如果处理器实现半精度扩展)

在ARMv7上,NEON指令可处理:
(1)、双字向量:八个8位元素、四个16位元素、两个32位元素、一个64位元素;

(2)、四字向量:十六个8位元素、八个16位元素、四个32位元素、两个64位元素。

在ARMv7上,NEON中的正常指令、宽指令、窄指令、饱和指令、长指令:

(1)、正常指令:生成大小相同且类型通常与操作数向量相同的结果向量;

(2)、长指令:对双字向量操作数执行运算,生成四字向量的结果。所生成的元素一般是操作数元素宽度的两倍,并属于同一类型;

(3)、宽指令:一个双字向量操作数和一个四字向量操作数执行运算,生成四字向量结果。所生成的元素和第一个操作数的元素是第二个操作数元素宽度的两倍;

(4)、窄指令:四字向量操作数执行运算,并生成双字向量结果,所生成的元素一般是操作数元素宽度的一半;

(5)、饱和指令:当超过数据类型指定的范围则自动限制在该范围内。

在ARMv7上,有些NEON指令可处理与向量组合使用的标量:

NEON标量可以为8位、16位、32位或64位;

除乘法指令之外,访问标量的指令也可访问寄存器组中的任何元素;

指令语法通过在双字向量中使用索引来引用标量,从而使Dm[x]表示Dm中的第x个元素;

乘法指令仅允许使用16位或32位标量,并且只能访问寄存器组中的前32个标量;

这在乘法指令中意味着:(1)、16位标量限定为寄存器D0-D7,其中x位于范围0-3内;(2)、32位标量限定为寄存器D0-D15,其中x为0或1。

在ARMv7上,NEON支持的数据类型还包括{0,1}上的多项式,使用布尔算法规则处理系数0和1:

(1)、0+0=1+1+0;

(2)、0+1=1+0=1;

(3)、0*0=0*1=1*0=0;

(4)、1*1=1.

也就是说,将两个{0,1}上的多项式相加与按位异或的运算相同,而将两个{0,1}上的多项式相乘则与整乘的运算相同,但部分积执行的是异或运算,而不是相加运算。

向量寄存器

向量寄存器用来存放向量数据,每个向量元素的类型必须相同。向量寄存器根据处理元素的大小可以划分为 2/4/8/16 个通道。

AArch64 向量寄存器

AArch64 有 32 个 128bit 的向量寄存器,这些寄存器又可以划分为:

  • 32 个 128bit 的 V 寄存器,V0~V31。
  • 32 个 64bit 的 D 寄存器,D0~D31。
  • 32 个 32bit 的 S 寄存器,S0~S31。

每种类型寄存器的映射关系如下:

AArchh32 / ARMV7向量寄存器

AArch32/Armv7 有 16 个 128bit 的向量寄存器,这些寄存器又可以划分为:

  • 16 个128bit 的 Q 寄存器,Q0~Q15。
  • 32 个 64bit 的 D 寄存器,D0~D31。
  • 32 个 32bit 的 S 寄存器,S0~S31。

每种类型寄存器的映射关系如下:

汇编指令格式

 略

intrinsics指令格式

Intrinsics是一种编程技术,用于在高级语言中直接调用底层硬件指令。它允许开发人员利用底层硬件的并行计算能力和优化指令集,以提高程序的性能和效率。

在ARM架构中,有一套专门的Intrinsics函数,用于调用NEON指令集中的指令。通过使用这些Intrinsics函数,开发人员可以在高级语言中编写并行计算的代码,而无需直接编写汇编语言。这样可以提高开发效率,并且可以充分利用底层硬件的计算能力。

相比于汇编指令,NEON Intrinsics 是一种更简单的编写 NEON 代码的方法,NEON Intrinsics 类似于 C 函数调用,在编译时由编译器替换为相应的汇编指令,使用时需要包含头文件arm_neon.h

https://developer.arm.com/architectures/instruction-sets/intrinsics/

向量类型

向量类型分为非数组向量和数组向量

  1. 非数组向量格式 <type><size>x<number_of_lanes>_t
  2. 数组向量格式 <type><size>x<number_of_lanes>x<length_of_array>_t

<type> 数据类型,如 int/uint/float/poly

<size> 元素大小,如8/16/32/64。

<number_of_lanes> 通道数。

<length_of_array> 数组中元素个数。

                                         向量类型示意图

内联函数

函数格式如下:v<mod><opname><shape><flags>_<type>

mod表示计算的模式:

  • q:表示饱和计算
  • h:表示折半计算
  • d:表示加倍计算
  • r:表示舍入计算
  • p:表示pairwise计算

opname表示具体的计算指令,分类如下:

功能类别介绍
Load/Store对数据进行向量加载和存储,既可以对单个数据进行加载和存储,也可以对向量结构体数据进行加载和存储
Arithmetic对整数和浮点数向量加减运算
Multiply整型或浮点型的向量乘法运算,同时包含了乘法和加法混合运算,以及乘法和减法的运算的混合运算
Shift向量位移操作,其中位移数据可以是立即数也可以是向量
Logical and compare包含了逻辑运算(与或非运算等)和比较运算(等于、大于、小于等)
Floating-point包含了浮点和其他类型数据之间的相互转化操作
Permutation对向量进行重排操作
Misecllaneous标量数据赋值到向量的操作
Data processing一般性处理,极值操作、绝对值差、数值取反、平方根倒数等
Type conversion数值类型转换,数据的组合及提取等

shape表示有效长度:

  • l:表示long,输出向量的元素长度是输入长度的2倍
  • n:表示 narrow,输出向量的元素长度是输入长度的1/2倍
  • w:表示 wide,第一个输入向量和输出向量类型一样,且是第二个输入向量元素长度的2倍
  • _high:AArch64专用,而且和上面的l/n 配合使用。
    • 当使用 l(Long) 时,表示输入向量只有高 64bit 有效;  
    • 当使用 n(Narrow) 时,表示输出只有高 64bit 有效。
  • _n:表示有标量参与向量计算
  • _lane: 指定向量中某个通道参与向量计算

flags表示是128bit还是64bit:

  • q:表示使用 128bit 的向量,否则使用 64bit 的向量。

type表示类型:

表示单个通道的数据类型,有u8s8u16s16u32s32f32f64

内联函数结构示意图

Load/Store

  • Load以解交织的方式加载数据
// 以解交织方式加载数据到n个向量寄存器, n为1~4
Result_t vld[n]<q>_type(Scalar_t *p_addr);

// 以解交织方式加载数据到n个向量寄存器的第N通道, n为1~4
Result_t vld[n]<q>_lane_type(Scalar_t *p_addr, Vector_t M, int N);
  • Store以交织的方式存储数据
// 将n个向量寄存器数据以交织方式存储到内存中, n为1~4
void vst[n]<q>_type(Scalar_t* N, Vector_t M);

// 将n个寄存器的N通道数据以交织方式存储到内存中, n为1~4
void vst[n]<q>_lane_type(Scalar_t *p_addr, Vector_t M, int N);

2 个向量中多通道 load/store, 以及单个通道的load/store

Arithmetic

  • 整数和浮点数的加减运算。
// 基本的加减操作
Result_t vadd<q>_type(Vector1_t N, Vector2_t M);
Result_t vsub<q>_type(Vector1_t N, Vector2_t M);

// L(Long)类型的指令加减运算,输出向量长度是输入的两倍。
Result_t vaddl_type(Vector1_t N, Vector2_t M);
Result_t vsubl_type(Vector1_t N, Vector2_t M);

// W(Wide)类型的指令加减运算,第一个输入向量的长度是第二个输入向量长度的两倍。
Result_t vaddw_type(Vector1_t N, Vector2_t M);
Result_t vsubw_type(Vector1_t N, Vector2_t M);

// H(half)类型的加减运算;将计算结果除以2。
Result_t vhadd<q>_type(Vector1_t N, Vector2_t M);
Result_t vhsub<q>_type(Vector1_t N, Vector2_t M);

// Q(Saturated)饱和类型的加减操作
Result_t vqadd<q>_type(Vector1_t N, Vector2_t M);
Result_t vqsub<q>_type(Vector1_t N, Vector2_t M);

// RH(Rounding Half)类型的加减运算
Result_t vrhadd<q>_type(Vector1_t N, Vector2_t M);
Result_t vrhsub<q>_type(Vector1_t N, Vector2_t M);

// HN(half Narrow)类型的加减操作
Result_t vaddhn_type(Vector1_t N, Vector2_t M);
Result_t vsubhn_type(Vector1_t N, Vector2_t M);

// RHN(rounding half Narrow)类型的加减操作
Result_t vraddhn_type(Vector1_t N, Vector2_t M);
Result_t vrsubhn_type(Vector1_t N, Vector2_t M);

vhadd_s32 instrisics指令的操作

Multiply

  • 整型和浮点型的乘法运算, 参与计算的都是向量
// 基本乘法操作
Result_t vmul<q>_type(Vector1_t N, Vector2_t M);

// l(Long)类型的乘法操作
Result_t vmull_type(Vector1_t N, Vector2_t M);

// QDL(Saturated, Double, Long)类型的乘法操作
Result_t vqdmull_type(Vector1_t N, Vector2_t M);

// 基本的乘加和乘减操作
Result_t vmla<q>_type(Vector1_t N, Vector2_t M, Vector3_t P);
Result_t vmls<q>_type(Vector1_t N, Vector2_t M, Vector3_t P);

// L(Long)类型的乘加和乘减操作
Result_t vmlal_type(Vector1_t N, Vector2_t M, Vector3_t P);
Result_t vmlsl_type(Vector1_t N, Vector2_t M, Vector3_t P);

// QDL(Saturated, Double, Long)类型的乘加和乘减操作
Result_t vqdmlal_type(Vector1_t N, Vector2_t M, Vector3_t P);
Result_t vqdmlsl_type(Vector1_t N, Vector2_t M, Vector3_t P);

// QDLH(Saturated, Double, Long, Half)类型的乘法操作
Result_t vqdmulh<q>_type(Vector1_t N, Vector2_t M);

// QRDLH(Saturated, Rounding Double, Long, Half)类型的乘法操作
Result_t vqrdmulh<q>_type(Vector1_t N, Vector2_t M);
  • 带通道类型的乘法操作
// 基本的乘法操作
Result_t vmull_lane_type(Vector1_t N, Vector2_t M, int n);

// 基本的乘加和乘减操作
Result_t vmla<q>_lane_type(Vector1_t N, Vector2_t M, Vector3_t P, int n);
Result_t vmls<q>_lane_type(Vector1_t N, Vector2_t M, Vector3_t P, int n);

// L(long) 类型的乘加和乘减操作
Result_t vmlal_lane_type(Vector1_t N, Vector2_t M, Vector3_t P, int n);
Result_t vmlsl_lane_type(Vector1_t N, Vector2_t M, Vector3_t P, int n);

// QDL(Saturated, Double, long) 类型的乘加和乘减操作
Result_t vqdmlal_lane_type(Vector1_t N, Vector2_t M, Vector3_t P, int n);
Result_t vqdmlsl_lane_type(Vector1_t N, Vector2_t M, Vector3_t P, int n);

// QDH(Saturated, Double, Half) 类型的操作
Result_t vqdmulh<q>_lane_type(Vector1_t N, Vector2_t M, int n);

vmla_lane_s32 intrinsics 指令的操作

  • 向量和标量的乘法
// 基本的向量和标量的乘法
Result_t vmul<q>_n_type(Vector_t N, Scalar_t M);

// L(Long) 类型的向量和标量的乘法
Result_t vmull_n_type(Vector_t N, Scalar_t M);

// QDL(Saturated, Double, long) 类型的向量和标量的乘法
Result_t vqdmull_n_type(Vector_t N, Scalar_t M);

// QDH(Saturated, Double, Half) 类型的向量和标量的乘法
Result_t vqdmulh<q>_n_type(Vector_t N, Scalar_t M);

// QRDH(Saturated, Double, Half) 类型的向量和标量的乘法
Result_t vqrdmulh<q>_n_type(Vector_t N, Scalar_t M);

// L(Long) 类型的乘加和乘减操作
Result_t vmlal_n_type(Vector1_t N, Vector2_t M, Scalar_t P);
Result_t vmlsl_n_type(Vector1_t N, Vector2_t M, Scalar_t P);

// QDL(Saturated, Double, long) 类型的乘加和乘减
Result_t vqdmlal_n_type(Vector1_t N, Vector2_t M, Scalar_t P);
Result_t vqdmlsl_n_type(Vector1_t N, Vector2_t M, Scalar_t P);

Shift

  • 立即数类型的位移
// 基本的立即数左移和右移
Result_t vshr<q>_n_type(Vector_t N, int n);
Result_t vshl<q>_n_type(Vector_t N, int n);

// R(rounding) 类型的右移操作
Result_t vrshr<q>_n_type(Vector_t N, int n);

// QL(Saturated, long) 类型的右移操作
Result_t vqshl<q>_n_type(Vector_t N, int n);

// 右移累加操作
Result_t vsra<q>_n_type(Vector1_t N, Vector2_t M, int n);

// R(rounding) 类型的右移累加操作
Result_t vrsraq_n_type(Vector1_t N, Vector2_t M, int n);

// Q(Saturated) 类型的左移操作,而且输入是有符号,输出是无符号的
Result_t vqshluq_n_type(Vector_t N, int n);

// N(Narrow) 类型的右移操作
Result_t vshrn_n_type(Vector_t N, int n);

// QN(Saturated, Narrow) 类型的右移操作, 而且输入是有符号,输出是无符号的
Result_t vqshrun_n_type(Vector_t N, int n);

// QRN(Saturated, Rounding, Narrow) 类型的右移操作, 而且输入是有符号,输出是无符号的
Result_t vqrshrun_n_type(Vector_t N, int n);

// QN(Saturated, Narrow) 类型的右移操作
Result_t vqshrn_n_type(Vector_t N, int n);

// RN(Rounding, Narrow) 类型的右移操作
Result_t vrshrn_n_type(Vector_t N, int n);

// QRN(Rounding, Rounding, Narrow) 类型的右移操作
Result_t vqrshrn_n_type(Vector_t N, int n);

// N(Narrow) 类型的左移操作
Result_t vshll_n_type(Vector_t N, int n);
  • 非立即数类型的位移
// 左移
Result_t vshlq_type(Vector1_t N, Vector2_t M);

// Q(Saturated) 类型的左移操作
Result_t vqshl<q>_type(Vector1_t N, Vector2_t M);

// QR(Saturated, rounding) 类型的左移操作
Result_t vrshl<q>_type(Vector1_t N, Vector2_t M);
  • 移位并插入
// 将向量 M 中各个通道先右移动 n 位, 然后将移动后元素插入到 N 对应的元素中,
// 并保持 N 中每个元素的高 n 位保持不变
Result_t vsri<q>_n_type(Vector1_t N, Vector2_t M, int n);

// 将向量 M 中各个通道先左移动 n 位, 然后将移动后元素插入到 N 对应的元素中,
// 并保持 N 中第每个元素的低 n 位保持不变
Result_t vsli<q>_n_type(Vector1_t N, Vector2_t M, int n);

vsliq_n_u32 intrinsics 指令的操作

Logical and compare

eq 表示相等, ge 表示大于或等于, gt 表示大于, le 表示小于或等于, lt 表示小于
  • 逻辑比较操作,比较结果为true,输出向量的对应通道将被设置为全 1,否则设置为全0 
Result_t vceq<q>_type(Vector1_t N, Vector2_t M);
Result_t vcge<q>_type(Vector1_t N, Vector2_t M);
Result_t vcle<q>_type(Vector1_t N, Vector2_t M);
Result_t vcgt<q>_type(Vector1_t N, Vector2_t M);
Result_t vclt<q>_type(Vector1_t N, Vector2_t M);
  • 向量的绝对值比较,比较结果为true时,输出向量对应通道将被设置为全1,否则设置为全0
Result_t vcage<q>_type(Vector1_t N, Vector2_t M);
Result_t vcale<q>_type(Vector1_t N, Vector2_t M);
Result_t vcagt<q>_type(Vector1_t N, Vector2_t M);
Result_t vcalt<q>_type(Vector1_t N, Vector2_t M);

- 按位与\或\非\异或操作

Result_t vand<q>_type(Vector1_t N, Vector2_t M);
Result_t vorr<q>_type(Vector1_t N, Vector2_t M);
Result_t vmvn<q>_type(Vector_t N);
Result_t veor<q>_type(Vector1_t N, Vector2_t M);

vmvn_s32 intrinsics 指令操作

  • 元素与操作
// 按通道做与操作,为 true 时,将输出向量对应通道设置为全 1,否则设置为全 0
Result_t vtst<q>_type(Vector1_t N, Vector2_t M);
  • 其他
// M 作为 mask,标识是否对 N 做清零操作。当 M 中某位为 1, 则将 N 中对应位清零
Result_t vbic<q>_type(Vector1_t N, Vector2_t M);

// P 作为 mask,按位 select。当 P 中某位是 1 时,将选择 N 中对应位作为输出,否则选择 M
Result_t vbsl<q>_type(Vector1_t N, Vector2_t M, Vector3_t P);
Floating-point
  • 浮点数之间的转化, 以及浮点类型与整数类型之间的转化
// 单精度浮点转化为整数类型
Result_t vcvt<q>_type_f32(Vector_t N);

// 整数类型转化为单精度浮点
Result_t vcvt<q>_f32_type(Vector_t N);

// f16转化为f32
Result_t vcvt_f16_f32(Vector_t N);

// f32转化为f16
Result_t vcvt_f32_f16(Vector_t N);
  • 浮点类型的乘加操作
Result_t vfma<q>_type(Vector1_t N, Vector2_t M, Vector3_t P);
  • 浮点类型的乘减操作
Result_t vfms<q>_type(Vector1_t N, Vector2_t M, Vector3_t P);

vfms intrinsics 指令操作

Permutation

  • 向量提取组合操作
Result_t vext<q>_type(Vector1_t N, Vector2_t M, int n);

vextq_u8 intrinsics 指令操作

  • 查表操作
Result_t vtbl[n]_type(Vector1_t N, Vector2_t M);
Result_t vtbx[n]_type(Vector1_t N, Vector2_t M, Vector3_t P);
  • 向量翻转操作
Result_t vrev64<q>_type(Vector_t N);
Result_t vrev32<q>_type(Vector_t N);
Result_t vrev16<q>_type(Vector_t N);
vrev16<q>_type 按照 16bit 为块,块内数据按照 8bit 为单位进行翻转
vrev32<q>_type 按照 32bit 为块,块内数据按照 8bit,16bit 为单位进行翻转
vrev64<q>_type 按照 64bit 为块,块内数据按照8bit, 16bit, 32bit为单位进行翻转

vrev16_s8, vrev32_s8 intrinsics 指令操作

  • 旋转操作
旋转指令包含了两种矩阵旋转的指令, TRN1TRAN2
Result_t vtrn1<q>_type(Vector1_t N, Vector2_t M);
Result_t vtrn2<q>_type(Vector1_t N, Vector2_t M);

vtrn1q_s32, vtrn2q_s32 intrinsics 指令操作

  • 向量交织和解交织操作
// 交织操作
Result_t vzip<q>_type(Vector1_t N, Vector2_t M);

// 解交织操作
Result_t vuzp<q>_type(Vector1_t N, Vector2_t M);

vzip_u8 intrinsics 指令操作

Miscellaneous

  • 将同一个标量填充到每个向量通道
Result_t vcreate_type(Scalar_t N);
Resutl_t vdup_type(Scalar_t N);
Result_t vdup_n_type(Scalar_t N);
Result_t vdupq_n_type(Scalar_t N);
Result_t vmov_n_type(Scalar_t N);
Result_t vmovq_n_type(Scalar_t N);
  • 将向量中某个通道的数据填充到指定的向量中
Result_t vdup<q>_lane_type(Vector_t N, int n);

vdup_lane_s32 intrinsics 指令操作

Data processing

  • max\min操作
// 基本的 max, min
Result_t vmax<q>_type(Vector1_t N, Vector2_t M);
Result_t vmin<q>_type(Vector1_t N, Vector2_t M);

// pairwise 类型的 max, min
Result_t vpmax_type(Vector1_t N, Vector2_t M);
Result_t vpmin_type(Vector1_t N, Vector2_t M);

vpmin_s16 intrinsics 指令操作

  • 差的绝对值操作
// 基本的绝对值计算
Result_t vabs<q>_type(Vector_t N);

// 差的绝对值操作
Result_t vabd<q>_type(Vector1_t N, Vector2_t M);

// L(Long)类型, 差的绝对值
Result_t vabdl_type(Vector1_t N, Vector2_t M);

// 差的绝对值,并和另一个向量相加
Result_t vaba<q>_type(Vector1_t N, Vector2_t M, Vector3_t P);

// L(Long)类型, 差的绝对值,并和另一个向量相加, 输出是输入长度的两倍
Result_t vabal_type(Vector1_t N, Vector2_t M, Vector3_t P);
  • 取反操作
// 基本的取反操作
Result_t vneg<q>_type(Vector_t N);

// Q(Saturated)类型,带饱和的取反操作
Result_t vqneg<q>_type(Vector_t N);
  • 按位统计 0 或 1 的个数
// 统计每个通道 1 的个数
Result_t vcnt<q>_type(Vector_t N);

// 从符号位开始,统计每个通道中与符号位相同的位的个数,且这些位必须是连续的
Result_t vcls<q>_type(Vector_t N);

// 从符号位开始,统计每个通道连续0的个数
Result_t vclz<q>_type(Vector_t N);
  • 倒数和平方根求倒计算
// 对每个通道近似求倒
Result_t vrecpe<q>_type(Vector_t N);

// 对每个通道使用 newton-raphson 求倒
Result_t vrecps<q>_type(Vector1_t N, Vector2_t M);

// 对每个通道平方根近似求倒
Result_t vrsqrte<q>_type(Vector_t N);

// 对每个通道使用 newton-raphson 平方根近似求倒
Result_t vrsqrts<q>_type(Vector1_t N, Vector2_t M);
  • 向量赋值
// N(Narrow) 类型的赋值,取输入每个通道的高半部分,赋给目的向量
Result_t vmovn_type(Vector_t N);

// L(long) 类型的赋值,使用符号拓展或者 0 拓展的方式,将输入通道的数据赋给输出向量
Result_t vmovl_type(Vector_t N);

// QN(Saturated, Narrow) 类型的赋值,饱和的方式赋值,输出是输入宽度的两倍
Result_t vqmovn_type(Vector_t N);

// QN(Saturated, Narrow) 类型的赋值,饱和的方式赋值,输出是输入宽度的两倍,而且输入为有符号数据,输出无符号
Result_t vqmovun_type(Vector_t N);

Type conversion

  • 元素类型的重新解释
Result_t vreinterpret<q>_DSTtype_SRCtype(Vector1_t N);
  • 两个 64bit 向量组合成一个 128bit 向量
Result_t vcombine_type(Vector1_t N, Vector2_t M);
  • 提取 128bit 向量的高半部分或则低半部分
Result_t vget_high_type(Vector_t N);
Result_t vget_low_type(Vector_t N);

vget_low_s32 \ vget_high_s32 intrinsics 指令操作

NEON intrisics 指令在x86平台的仿真

为了便于 NEON 指令从 ARM 平台移植到 x86 平台使用,Intel 提供了一套转化接口 NEON2SSE,用于将 NEON 内联函数转化为 Intel SIMD(SSE) 内联函数。大部分 x86 平台 C/C++编译器均支持 SSE,因此只需下载并包含接口头文件NEON_2_SSE.h,即可在x86平台调试 NEON 指令代码。

#ifdef ARM_PLATFORM
#  include <arm_neon.h>
#else
#  include "NEON_2_SSE.h"
#endif
NEON2SSE 提供了 1700 多个 NEON 内联函数的转换接口,运算结果确保与 ARM 平台准确一致。

性能方面:

  • 对于使用 128 位向量运算的 NEON 操作,NEON2SSE 在 x86 平台能得到与 ARM 类似的加速比;
  • 如果使用 64 位向量做 NEON 运算,x86 平台的加速比将低于 ARM 平台。

 参考

ARM技术杂谈:何谓FPU、VFP、ASE、NEON、MPE、SVE、SME以及MVE

NEON 

Armv8上不弃不离的NEON/FPU

ARM FPU 加速浮点计算介绍

ARM Cortex-A Series Programmer’s Guide for ARMv7-A

ARM Cortex-M7 Processor Technical Reference Manual r0p2:About the FPU

IEEE 754浮点数标准详解

arm浮点运算

CPU 优化技术-NEON 指令介绍 - 知乎​​​​​​

 ARM Neon Programmer's Guide

 ARM NEON programming quick reference

 ARM Architecture Reference Manual Armv8, for A-profile architecture

Intrinsics – Arm Developer

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/724356.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构与算法】魔王语言解释(C/C++)

实践要求 1. 问题描述 有一个魔王总是使用自己的一种非常精炼而抽象的语言讲话&#xff0c;没有人能听懂。但他的语言是可以逐步解释成人能懂的语言的&#xff0c;因为他的语言是由以下两种形式的规则由人的语言逐步抽象上去的: 形式一 α → β 1 β 2 . . . β m \alpha \…

无线耳机推荐的品牌有哪些?八款无线蓝牙耳机推荐

无线蓝牙耳机无疑是当前最受欢迎的数码产品之一&#xff0c;平听闲暇时刻听听歌或者是运动健身&#xff0c;常常能看到蓝牙耳机的身影&#xff0c;作为一个热衷于听歌的精致boy&#xff0c;佩戴过的蓝牙耳机数不胜数&#xff0c;现在&#xff0c;除了手机品牌会开发无线蓝牙耳机…

西电_矩阵论_学习笔记

文章目录 【 第一章 线性空间 】【 第二章 范数 】【 第三章 矩阵函数 】【 第四章 矩阵分解 】【 第五章 矩阵特征值估计 】【 第六章 广义逆 】【 考试重点内容总结 】 这是博主2023春季西电所学矩阵论的思维导图&#xff08;软件是幕布&#xff09;&#xff0c;供大家参考&a…

ROS:工作空间覆盖

目录 一、概念二、示例2.1操作2.2原因 三、存在的问题 一、概念 所谓工作空间覆盖&#xff0c;是指不同工作空间中&#xff0c;存在重名的功能包的情形。 ROS 开发中&#xff0c;会自定义工作空间且自定义工作空间可以同时存在多个&#xff0c;可能会出现一种情况: 虽然特定工…

【数学建模】 灰色预测模型

数学建模——预测模型简介 https://www.cnblogs.com/somedayLi/p/9542835.html 灰色预测模型 https://blog.csdn.net/qq_39798423/article/details/89283000?ops_request_misc&request_id&biz_id102&utm_term%E7%81%B0%E8%89%B2%E9%A2%84%E6%B5%8B%E6%A8%…

开放式耳机哪个好?2023开放式耳机排行榜推荐

​耳机成为了当代青年必不可少的一款数码单品&#xff0c;无论在什么时间、哪个地点总能看到很多人戴着耳机。耳机也分有很多类型&#xff0c;就比如市面上大火的开放式耳机&#xff0c;很多人还不清楚开放式耳机如何挑选的&#xff0c;下面我来推荐几款很不错的开放式耳机&…

【一】部署Zabbix监控详解

Zabbix监控 1.Zabbix监控概述1.1 zabbix是什么1.2 zabbix监控原理1.3 Zabbix 6.0新特性1.4 Zabbix 6.0功能组件1.5 Zabbix与prometheus区别对比 2. 部署Zabbix6.02.1 安装NginxPHP2.2 部署Mariadb数据库2.3 安装zabbix Server服务端2.4 部署Web前端&#xff0c;进行访问2.5 部署…

软件测试中的二八定律到底是什么?

目录 前言&#xff1a; 一、80%的软件缺陷&#xff0c;集聚在软件20%的模块中 二、软件测试工作尽早介入 三、反映在软件测试的自动化方面 四、80%的缺陷&#xff0c;集中在某20%的开发工程师代码中&#xff1b; 一、缺陷是解决不完的 二、是不可能发现100%缺陷的 三、…

Web APls-day04

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 日期对象 日期对象&#xff1a;用来表示时间的对象 作用&#xff1a;可以得到当前系统时间 实例化 在代码中发现…

【设计模式】第九章:外观模式(门面模式)详解及应用案例

系列文章 【设计模式】七大设计原则 【设计模式】第一章&#xff1a;单例模式 【设计模式】第二章&#xff1a;工厂模式 【设计模式】第三章&#xff1a;建造者模式 【设计模式】第四章&#xff1a;原型模式 【设计模式】第五章&#xff1a;适配器模式 【设计模式】第六章&…

17 MFC进程通信

文章目录 剪切板管道匿名管道父进程写入数据子进程读出数据 命名管道 邮槽邮槽服务器邮槽客户端 剪切板 设置界面 发送 //设置剪切板数据 void CClipboardDlg::OnBnClickedBtnSend() {UpdateData(TRUE);if (m_strSend.IsEmpty()){MessageBox(L"请输入需要设置的文本&quo…

spring的事务处理@Trasactional Aop处理第二弹

书接上回 &#xff0c;我们针对spring中的ApplicationContext类的扩展功能-事件发布和监听处理源码进行了解析&#xff0c;知道了消息是如何存放和传递给监听器处理的。这章我们看下另外一个重量级的组件Transactional事务处理是如何实现的。 我们可能都了解过声明式的事务处理…

十三、弹性容器flex的样式1

目录&#xff1a; 1.基础准备 2.属性解析 一、基础准备 设置ul为弹性元素&#xff0c;默认是flex-direction:row&#xff0c;所以不用设置&#xff0c;然后在让里面的方块不进行伸缩。 我们看到小方块超出了边框 <style>*{margin: 0;padding: 0;list-style: none;}ul{wi…

vant List组件实现上拉加载中 首次进行load事件执行两次的问题

需求&#xff1a; 进行tab切换时&#xff0c;其中一次tab下有上拉加载的功能 问题&#xff1a; 在第一次切换到带有上拉加载列表功能的tab&#xff0c;执行加载list的load事件执行了两次造成数据的重复加载&#xff0c;另外如果这个list的高度全部在可视范围内&#xff0c;首次…

STM32:Custom HID实现USB双向通信

本文章主要讲了使用STM32的USB Device&#xff0c;实现控制板和电脑通信功能。从而实现&#xff0c;上位机对控制板进行调试。 USB Device可以有多种类型&#xff0c;实现双向通信的话&#xff0c;推荐使用Custom HID类型。 首先使用STM32CubeMx实现功能引脚配置并且生成对应…

Win11的两个实用技巧系列之关闭输入法悬浮窗方法、记住窗口位置禁用或启用的方法

Win11输入法悬浮窗怎么去掉? win11关闭输入法悬浮窗方法 Win11输入法悬浮窗怎么去掉&#xff1f;win11安装的输入法有悬浮窗&#xff0c;想要去掉悬浮窗&#xff0c;该怎么操作呢&#xff1f;下面我们就来看看win11关闭输入法悬浮窗方法 很多用户将自己win11更新到了最新版本…

面试题 16.02. 单词频率

设计一个方法&#xff0c;找出任意指定单词在一本书中的出现频率。 你的实现应该支持如下操作&#xff1a; WordsFrequency(book)构造函数&#xff0c;参数为字符串数组构成的一本书get(word)查询指定单词在书中出现的频率 示例&#xff1a; WordsFrequency wordsFrequency …

git cherry-pick 用法

1. 切换到目标分支 说明&#xff1a;本人基于 master 新建分支 master-fxd&#xff0c;那么目标分支为 master-fxd git checkout <target-branch>2. 从其他分支选择并应用单个提交 说明&#xff0c;其他分支例如 dev 提交的代码&#xff0c;使用 jihulab.com 比较 master…

针对WordPress程序无法升级最新版本的问题分析

WordPress程序是当前使用率最高的CMS系统之一&#xff0c;因开发功能完善&#xff0c;WordPress模板和插件众多而著称&#xff0c;茹莱神兽做三个网站&#xff0c;其中有两个网站使用的是WordPress程序搭建&#xff0c;可见它的受欢迎程度。 而WordPress程序本身也相当给力&a…

刷简单的题也很吃力怎么办?(经验分享)

目录 一、前言 1.刷简单的题也很吃力怎么办&#xff1f; 2.不重视这种问题会怎么样&#xff1f; 二、找到属于自己的解决方案 三、根据问题进行分解或建立思维导图​​​​​​​ 四、分享刷题网站 一、前言 1.刷简单的题也很吃力怎么办&#xff1f; 有的时候在当时学完…