0%

SIMD 指令集

SIMD(Single Instruction, Multiple Data):单指令多数据流,实质是通过数据并行来提高执行效率。

1. ARM 架构

1.1 ARMv1 - ARMv9

arm_architech.png

  • 基于 ARMv7 版本的 ARM Cortex 系列产品由 A、R、M 三个系列组成,具体分类延续了一直以来 ARM 面向具体应用设计 CPU 的思路。
    arm_cortex.png

1.2 AArch64/AArch32

  • ARMv8 的两种执行状态: AArch64/AArch32
    • AArch64:Armv8-A 架构中引入的 64 位执行状态,执行 A64 指令,使用64bit的通用寄存器
    • AArch32:兼容 Armv7-A 和先前的 32 位 Arm 架构的 32 位执行状态,执行A32/T32指令,使用32bit的通用寄存器
  • ARMv8 支持浮点类型的除法向量操作,这是 ARMv7 没有的。另外 AArch64 还支持 double 类型的操作。
  • Arm Compiler
    arm_complier.png
    • –target=aarch64-arm-none-eabi 生成 AArch64 的可执行程序。默认使用 ARMv8-A target(处理器),也可使用 -mcpu 指定特定的 ARMv8 处理器。
    • –target=arm-arm-none-eabi 生成 AArch32的可执行程序。对 AArch32 而言,没有默认target(处理器),所以需要使用 -march 或者 -mcpu 来指定处理器,如:–target=arm-arm-none-eabi -mcpu=cortex-a53

2. SIMD

SIMD(Single Instruction, Multiple Data):单指令多数据流,实质是通过数据并行来提高执行效率。

2.1 x86 指令集

x86_SIMD.png

2.2 arm 指令集 - NEON

2.2.1 寄存器

  • 向量寄存器用来存放向量数据,每个向量元素的类型必须相同。

  • 向量寄存器根据处理元素的大小可以划分为 2/4/8/16 个通道。

arm_register.png

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

    • 32 个 128bit 的 V 寄存器,V0~V31。
    • 32 个 64bit 的 D 寄存器,D0~D31。
    • 32 个 32bit 的 S 寄存器,S0~S31。
  • AArch32/ARMv7 有 16 个 128bit 的向量寄存器,这些寄存器又可以划分为:

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

2.2.2 汇编指令格式

{<prefix>}<op>{<suffix>} Vd.<T>, Vn.<T>, Vm.<T>

如:

  • :前缀名字,包括以下几类:
    • S/U/F/P:数据类型,分别为 有符号整型/无符号整型/浮点型/布尔型。
    • Q:饱和(Saturating)计算。
    • R:舍入(Rounding)计算, Rounding 操作等价于加上 0.5 之后再截断。
    • H:折半(Halving)计算。
    • D:翻倍(Doubling)算。
  • :具体的操作,例如 ADD,SUB 等等
  • :后缀名字,包括以下几类:
    • V:Reduction 计算。
    • P:Pairwise 计算。
    • H:结果只取每个通道的高半部分(High)。
    • L/N/W/L2/N2/W2:数据长度的变化
      • L/L2 :输出向量是输入向量长度的 2 倍,其中 L 表示输入寄存器的低 64bit 数据有效,L2 表示输入寄存器的高 64bit 数据有效。
      • N/N2:输出向量是输入向量的 1/2 倍,N 表示输出向量只有低 64bit 有效,N2 则表示输出向量只有高 64bit 有效。
      • W/W2:输出向量和第一个输入向量长度相等,且这两个向量是第二个向量长度的 2 倍,其中 W 表示第二个输入向量的低 64bit 有效,W2 表示第二输入向量的高 64bit 有效。
  • :单个通道的数据类型,8B/16B/4H/8H/2S/4S/2D,B 表示 8bit,H 表示 16bit,S 表示 32bit,D 表示 64bit

2.2.3 intrinsics 指令格式

2.2.3.1 向量类型格式

vector_type.png
非数组向量:<type><size>x<number_of_lanes>_t

数组向量:<type><size>x<number_of_lanes>x<length_of_array>_t

  • <type> 数据类型,如 int / uint / float。
  • <size> 元素大小,如8/16/32/64。
  • 通道数。
  • 数组中元素个数。

2.2.3.2 NEON 内联函数格式

function_type.png

v<mod><opname><shape><flags>_<type>

instrinsics.png

  • 举例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // a 加 b 的结果做饱和计算
    int8x8_t vqadd_s8(int8x8_t a, int8x8_t b);
    // a 减 b 的结果右移一位
    int8x8_t vhsub_s8(int8x8_t a, int8x8_t b);
    // a 乘 b 的结果扩大一倍, 最后做饱和操作
    int32x4_t vqdmull_s16(int16x4_t a, int16x4_t b);
    // 将 a 与 b 的和减半,同时做 rounding 操作, 每个通道可以表达为: (ai + bi + 1) >> 1
    int8x8_t vrhadd_s8(int8x8_t a, int8x8_t b);
    // 将 a、b 向量的相邻数据进行两两和操作
    int8x8_t vpadd_s8(int8x8_t a, int8x8_t b);
    // l:long,输出向量的元素长度是输入长度的 2 倍
    uint16x8_t vaddl_u8(uint8x8_t a, uint8x8_t b)
    // n:narrow,输出向量的元素长度是输入长度的 1/2
    uint32x2_t vmovn_u64(uint64x2_t a)
    // wide,第一个输入向量和输出向量类型一样,且是第二个输入向量元素长度的 2 倍
    uint16x8_t vsubw_u8(uint16x8_t a, uint8x8_t b)

Ref.