simd
1. x86
头文件 #include <immintrin.h>
编译选项加 -msse -mavx -mavx2,或者 cmake 加 target_compile_options(${PROJECT_NAME} PRIVATE -msse -mavx -mavx2)
MMX 对应 64 位,SSE 对应 128 位,AVX AVX2 对应 256 位,AVX512 对应 512 位,不过不绝对
查看支持哪些指令集:cat /proc/cpuinfo
1.1. 数据类型
__m64包含 2 个 float 类型数据的向量__m128包含 4 个 float 类型数据的向量__m128i包含 4 个 int 类型数据的向量__m128d包含 4 个 double 类型数据的向量__m256包含 8 个 float 类型数据的向量__m256i包含 8 个 int 类型数据的向量__m256d包含 8 个 double 类型数据的向量
1.2. 函数
_mm<bit>_<opt>_<data_type>
bit 可以是空、256、512
data_type
- ps:float
- pd:double
- epi8/epi16/epi32/epi64 有符号
- epu8/epu16/epu32/epu64 无符号
- m128/m128i/m128d/m256/m256i/m256d ?
- si128/si256 ?
opt
load(T *), set(T...)内存加载到寄存器- add, sub, mul, div 加减乘除
- store 寄存器写入到内存
- cast 数据类型比较
- compare 数据比较
logical<and|or|test>逻辑运算- mask 条件判断
初始化操作
xxx_load_xxx(T *)需要内存按要求对齐xxx_set_xxx(T...)(参数是反的)- 用 initialize_list
指定字节对齐 __attribute__((aligned(16))) float A[100];
1.3. 预取
_mm_prefetch(const char *p, int i)
预取 p 位置的内存,可能可用 __builtin_prefetch 替代
i 的取值
- _MM_HINT_T0 预取到 L1 Cache
- _MM_HINT_T1 预取到 L2 Cache
- _MM_HINT_T2 预取到 L3 Cache
1.4. 一些操作
__m256d _mm256_movedup_pd(__m256d)将第 2k 个 64bit 复制给 2k-1
2. arm sve
sve 是可扩展向量,如果编译时不指定向量宽度,二进制可以跑在所有宽度的机器上。但大多数时候会指定宽度,这样仍然有很好的可移植性。
32 个向量寄存器 z0..z31
- 宽度 128..2048-bit 且是 128 的倍数
16 个预测寄存器 p0..p15
- 宽度不确定
- 类似掩码,控制向量寄存器对应值是否生效
- 对应关系:向量寄存器的一个元素的最低位 × 预测处理器长度 / 向量寄存器长度,该位置预测处理器为 1 则开启(一般可能用不到)
b h s d q 分别是 8 16 32 64 128 为一个单元
需要注意
- 有些函数有简写形式,通过参数类型来判断,类似重载?
- 向量类型、预测类型长度都是运行时确定的
- 函数末尾的
_z改为_x可能可以少个 movprfx 指令
头文件 #include <arm_sve.h>
编译参数 -march=armv8-a+sve+sve2
向量类型
svint8_t..svint64_tsvuint8_t..svuint64_tsvfloat16_t..svfloat64_t
预测类型 svboot_t
svptrue_b32()32 位,所有元素有效svwhilelt_b32(i, n)32 位,index + i < n为有效svptest_any(a, b)判断两个预测类型有交集
查询
svcntb()向量包含几个单元。最后一个字母 b h w d 表示 1 2 4 8 字节
读写内存
svld1(p, scalar_ptr)读内存,返回向量类型svst1(p, scalar_ptr, vec)写内存
运算
svmla_z(p, vec_x, vec_y, scalar)返回 vec_x + vec_y × sca
类型转换
svcvt_f32_z转换到 fp32,经常配合 zip / uzp 一起使用
argmax:
cpp
svint32_t index;
svfloat32_t max = svdup_f32(-inf);
for (int i = 0; i < n; i += vl) {
svfloat32_t v = svld1(pg, ptr + i);
svbool_t cmp = svcmpge(pg, max, v);
max = svsel(cmp, max, v);
index = svsel(cmp, index, svindex_s32(i, 1));
}
svfloat32_t maxv = svmaxv(pg, max);
return svminv(svcmpeq(pg, max, maxv), index);3. arm neon
寄存器宽度固定 128-bit
cpp
#include <arm_neon.h>