SSE 指令集
Reference:
相关文章:
1. 数据类型
SSE 指令有
3
3
3 种类型,分别为 __m128
、__m128i
、__m128d
,每一种类型都以:__
+m
(猜测是xmm或emm)+128
(向量化的向量长度)+数据类型
构成。
-
__m128
:包含 4 4 4 个float
类型数据的向量。 -
__m128i
:包含若干个整型数据的向量,如char
、short
、int
、unsigned long long
等。例如 128 128 128 位的向量可以包含 16 16 16 个char
、 8 8 8 个short
、 4 4 4 个int
,这些整型既可以是有符号的也可以是无符号的。 -
__m128d
:包含 2 2 2 个 double 类型数据的向量。
有的后面还带个 u
,如 __m128_u
、__m128i_u
、__m128d_u
,与上面类型相同,但是是未对齐的版本。(定义的时候多一个__aligned__ (1))
2. 函数命名约定
_mm_<name>_<data_type>
<name>
:描述了内联函数的算数操作;<data_type>
:标识函数主要参数的数据类型。
3. emmintrin.h和xmmintrin.h的区别
emmintrin.h
:- 对应于SSE2(Streaming SIMD Extensions 2)指令集。
- 提供了一组用于执行128位数据操作的函数和宏,适用于大多数x86处理器。
- 定义了数据类型 __m128i 和 __m128d,允许处理单精度浮点数和整数数据。
- 适用于处理更广泛的数据类型和运算,包括整数和浮点数的操作。
xmmintrin.h
:- 对应于SSE指令集的原始版本,通常称为SSE1(Streaming SIMD Extensions 1)指令集。
- 提供了一组用于执行128位数据操作的函数和宏,早期的x86处理器支持。
- 定义了数据类型 __m128。
- 适用于处理主要是单精度浮点数操作的应用,但功能相对较少。
3. 指令写法
3.1 类型定义
类型定义的大致写法如下:
typedef float __m128_u __attribute__ ((__vector_size__ (16), __may_alias__, __aligned__ (1)));
typedef float __m128_u
: 这是一个typedef
语句,它创建了一个新的数据类型别名__m128_u
,它被定义为float
类型。__attribute__ ((__vector_size__ (16), __may_alias__, __aligned__ (1)))
: 这是GCC(GNU Compiler Collection)的属性语法,用于为__m128_u
类型添加一些属性。- vector_size (16): 这个属性指定了该类型的大小为 16 字节(128位),这使得 __m128_u 可以在一个128位寄存器中存储。
- may_alias: 这个属性告诉编译器,指针类型可以别名为 __m128_u,这是因为这种类型的别名可能会导致一些类型别名的规则和内存别名的优化问题。这是为了允许更灵活的内存访问,但可能会导致某些问题,因此在使用时需要小心。
- aligned (1): 这个属性指定了类型的对齐方式为1字节。这是一个不寻常的对齐方式,可能是为了在内存中更紧凑地存储数据。(不加这句话的这里是 16 16 16 字节对齐)
综合起来,这段代码定义了一个名为 __m128_u
的数据类型,其实质上是一个16字节的 float 数组。这种类型的定义可能会用于某些特殊情况下,例如需要进行未对齐内存访问的场景。但是,这段代码的含义可能会因为上下文和编译器而有所不同,因此在使用时需要考虑到相关的编译器行为和优化。
3.2 函数定义
函数定义的大致写法如下:
extern __inline __m128 __attribute__((__gnu_inline__, __always_inline__, __artificial__))
_mm_undefined_ps (void)
{
__m128 __Y = __Y;
return __Y;
}
-
extern __inline __m128 __attribute__((__gnu_inline__, __always_inline__, __artificial__)) _mm_undefined_ps(void)
:这是一个函数的声明,它指定了函数的返回类型为__m128
,函数名为_mm_undefined_ps
,参数为空。它使用了一系列的属性(attributes)来影响函数的行为和编译器的优化。- extern __inline: gcc 的 extern inline 行为十分古怪(参考 C 语言中的冷门知识点:extern inline 函数):
- 一个 extern inline 的函数只会被内联进去,而绝对不会生成独立的汇编代码段!即使是通过指针应用或者是递归调用也不会让编译器为它生成汇编代码,在这种时候对此函数的调用会被处理成一个外部引用;
- 另外,extern inline 的函数允许和外部函数重名,即在存在一个外部定义的全局库函数的情况下,再定义一个同名的 extern inline 函数也是合法的。
- attribute((gnu_inline, always_inline, artificial)): 这是一系列的属性,用于进一步指定内联函数的行为。
- gnu_inline: 告诉编译器遵循GNU内联规范,可能会影响编译器优化。
- always_inline: 强制编译器始终内联该函数,即使可能增加代码的体积。
- artificial: 表示这个函数是由编译器生成的,而不是由开发人员编写的。这可以影响一些优化行为。
- extern __inline: gcc 的 extern inline 行为十分古怪(参考 C 语言中的冷门知识点:extern inline 函数):
-
__m128 __Y = __Y;
:这是函数的内部实现,使用内联汇编。这个行汇编的实际作用是将一个未定义的值存储在 __m128 类型的变量 __Y 中。 -
return __Y;
:函数返回存储在 __Y 中的未定义的 __m128 值。
4. 指令
ps
:packed single-precision floating-point,表示这个函数用于存储打包的单精度浮点数,即 float。
epi
:explicit packed integer,表示对整数数据进行显式的打包; “e” 表示 “explicit”,意味着这个指令集中的操作是显式地针对整数数据的。
4.1 初始化函数
-
_mm_loadu_si128
创建一个向量,其中元素 0 0 0 为*P,其余元素为 0 0 0。__m128i _mm_loadu_si128 (__m128i_u const *__P)
-
_mm_set1_ps
创建一个 4 4 4 个元素都等于__F
的向量。__m128 _mm_set1_ps (float __F)
-
_mm_set1_epi8
创建一个包含相同 8 8 8 位整数__A
的向量。(也就是有 16 16 16 个相同整数)__m128 _mm_set1_epi8 (char __A)
-
_mm_setzero_ps
创建一个全是 0 0 0 的向量。__m128 _mm_setzero_ps (void)
-
_mm_setzero_si128
创建一个全是 0 0 0 的向量。__m128i _mm_setzero_si128 (void)
4.2 变量存储
-
_mm_store_ps
存储四个单精度浮点数Single Precision Floating Point(SPFP)
。地址必须是16字节对齐的。void _mm_store_ps (float *__P, __m128 __A)
-
_mm_storeu_ps
存储四个单精度浮点数Single Precision Floating Point(SPFP)
。地址不需要是16字节对齐的。void _mm_storeu_ps (float *__P, __m128 __A)
-
_mm_storel_epi64
将变量 B 中的第一个64位整型数(long long)存到地址 P 中。void _mm_storel_epi64 (__m128i_u *__P, __m128i __B)
4.3 算术操作
-
_mm_and_ps
对128位值执行 与 逻辑逐位操作。__m128 _mm_and_ps (__m128 __A, __m128 __B)
-
_mm_xor_ps
对128位值执行 异或 逻辑逐位操作。__m128 _mm_xor_ps (__m128 __A, __m128 __B)
-
_mm_sub_ps
分别对 A 和 B 中的四个 SPFP 值进行 A-B 操作。__m128 _mm_sub_ps (__m128 __A, __m128 __B)
-
_mm_sub_epi32
将 A-B 操作按32位进行处理,其中32位不能有溢出。每个元素被当做unsigned int
。__m128i _mm_sub_epi32 (__m128i __A, __m128i __B)
-
_mm_add_epi32
将 A+B 操作按32位进行处理,其中32位不能有溢出。每个元素被当做unsigned int
。__m128i _mm_add_epi32 (__m128i __A, __m128i __B)
-
_mm_mul_ps
分别对 A 和 B 中的四个 SPFP 值进行 A*B 操作。__m128 _mm_mul_ps (__m128 __A, __m128 __B)
-
_mm_max_epu8
执行无符号 8 8 8 位整数的最大值比较操作。__m128 _mm_max_epu8 (__m128i __A, __m128i __B)
4.4 比较操作
-
_mm_cmpgt_ps
对 A 和 B 的四个SPFP值执行比较。对于每个元素,如果寄存器 A 的元素大于 B 的对应元素,则结果寄存器中的相应mask将被设置为全1,否则设置为全0。__m128 _mm_cmpgt_ps (__m128 __A, __m128 __B)
-
_mm_cmpgt_epi8
比较a和b中打包的有符号8位整数是否大于,并将结果存储在dst中。注意比较的两个数为signed char
,而不是unsigned
:__m128i _mm_cmpgt_epi8 (__m128i a, __m128i b)
-
operation
FOR j := 0 to 15 i := j*8 dst[i+7:i] := ( a[i+7:i] > b[i+7:i] ) ? 0xFF : 0 ENDFOR
由于这种方式得到的每一个8为全是255,因此AVX512做了以下改进:
-
-
_mm_cmpgt_epi8_mask
比较a和b中打包的有符号8位整数的大于,并将结果存储在掩码向量k中。__mmask16 _mm_cmpgt_epi8_mask (__m128i a, __m128i b)
-
operation
FOR j := 0 to 15 i := j*8 k[j] := ( a[i+7:i] > b[i+7:i] ) ? 1 : 0 ENDFOR k[MAX:16] := 0
-
4.5 类型转换
-
_mm_cvtepi32_ps
将 A 中打包的 32 位整型转换为打包的 32 位浮点数,并将结果存储在 dst 中。__m128 _mm_cvtepi32_ps (__m128i __A)
-
_mm_cvtps_epi32
将 A 中打包的 32 位浮点数转换为打包的 32 位整型,并将结果存储在 dst 中。__m128i _mm_cvtps_epi32 (__m128 __A)
4.6 pack类
_mm_packs_epi32
将 A 和 B 中打包好的 32 位带符号的整型数截断成 16 位带符号的整型数,并将结果存储在 dst 中。__m128i _mm_packs_epi32 (__m128i __A, __m128i __B)
4.7 shift类
_mm_srli_si128
将 a 右移 imm8 个字节,同时填入零,并将结果返回。__m128i _mm_packs_epi32 (__m128i a, int imm8)