SSE
全称Streaming SIMD Extension,是x86上对SIMD指令集的一个扩展,主要用于处理单精度浮点数。Intel陆续推出SSE2、SSE3、SSE4版本。其中,SSE主要处理单精度浮点数,SSE2引入了整数的处理,
SSE指令集引入了8个128bit的寄存器,称为XMM0
到XMM7
。正因为这些寄存器存储了多个数据,使用一条指令处理,因此称这项功能为SIMD。
处理器指令
(暂无)
C程序库
这里是Intel官方的指令集C库文档地址:https://software.intel.com/sites/landingpage/IntrinsicsGuide/
以及Intel的介绍性文档:https://software.intel.com/en-us/node/523354
头文件
适用于使用gcc以及Intel x64平台上的编程。
指令集 | 头文件 |
---|---|
SSE | xmmintrin.h |
SSE2 | emmintrin.h |
SSE3 | pmmintrin.h |
SSSE3 | tmmintrin.h |
SSE4.1 | smmintrin.h |
SSE4.2 | nmmintrin.h |
SSE4A | ammintrin.h |
新数据结构
__m128
:保存4个单精度浮点数__m128d
:保存2个双精度浮点数__m128i
:保存整数,如4个32bit、8个16bit和2个64bit整数
对于__m128d
和__m128i
,编译器自动把局部和全局变量在栈上对齐16字节。若需要对齐整型、float和double数组,使用__declspec(align)
。
获取__m128i
的数据
获取8bit整数使用:
#define _mm_extract_epi8(x, imm) \
((((imm) & 0x1) == 0) ? \
_mm_extract_epi16((x), (imm) >> 1) & 0xff : \
_mm_extract_epi16(_mm_srli_epi16((x), 8), (imm) >> 1))
获取16bit整数使用:
int _mm_extract_epi16(__m128i a, int imm)
获取32bit整数使用:
#define _mm_extract_epi32(x, imm) \
_mm_cvtsi128_si32(_mm_srli_si128((x), 4 * (imm)))
获取64bit整数使用:
#define _mm_extract_epi64(x, imm) \
_mm_cvtsi128_si64(_mm_srli_si128((x), 8 * (imm)))
其中的imm
,是数字的下标。
函数命名规定
一般情况下函数的命名格式如下
_<mm/mm256/mm512>_<intrin_op>_<suffix>
其中,<intrin_op>
为固有指令的名称。<suffix>
为操作数的类型,前一个或前两个字母代表这个操作数是packed(p),还是extended packed(ep),还是scaler(s),后面的字母代表数据类型。如下:
- s:单精度浮点数
- d:双精度浮点数
- i128:有符号128bit整数
- i64:有符号64bit整数
- u64:无符号64bit整数
- i32:有符号32bit整数
- u32:无符号32bit整数
- i16:有符号16bit整数
- u16:无符号16bit整数
- i8:有符号8bit整数
- u8:无符号8bit整数
关于packed的解释,原意是完全的、满的,在这篇文章,Kittur解释了packed是指多个数据放到了一个向量里面,如4个单精度浮点数放到128bit寄存器,然后一条指令就操作这四个数。scaler类型的运算,一条指令只操作最低的某类型的数。extended packed则是用在SSE4引入的数据位数扩展的指令(看这里),可以将低位数的数据扩展到更高位数的数据,有有符号和无符号两种扩展方式。
变量后的数字,代表使用packed数据的第几个数据,如r0
则使用r
的最低位数据。
packed数据从右向左存储,也就是低位到高位存储,如
double a[2] = {1.0, 2.0};
__m128d t = _mm_load_pd(a);
那么在寄存器中,就是这样的
127----063----000
| 2.0 | 1.0 |
下面两条语句的效果是一样的
__m128d t = _mm_set_pd(2.0, 1.0);
__m128d t = _mm_setr_pd(1.0, 2.0);
packed类型的内嵌原语操作,将会操作寄存器中的所有数字。而scaler类型的操作,只会操作寄存器中最低位的数字,而其他的数字,从第一个操作数传递到结果当中。 下面的表格中,就有这两种类型的内嵌原语,不再说明这一点。
内嵌原语列表
下面的所有的操作,有些可能包含多条指令(如composite),因此可能不会达到预期的效果,请留意。为阐述方便,规定第一个参数为a,第二个为b,特殊操作数imm等另外说明。
转换原语查询表
列表头为被转换类型,行表头为转换到的类型
矢量转换
ps | pd | pi32 | pi64 | pi16 | pu16 | pi8 | pu8 | |
---|---|---|---|---|---|---|---|---|
ps | 1 | 1+t | 1 | 1 | ||||
pd | 1 | 1+t | ||||||
pi32 | 1 | 1 | ||||||
pi64 | ||||||||
pi16 | 1 | |||||||
pu16 | 1 | |||||||
pi8 | 1 | |||||||
pu8 | 1 |
标量转换
ss | sd | si32 | si64 | float32 | float64 | |
---|---|---|---|---|---|---|
ss | 1 | 1+t | 1+t | 1 | ||
sd | 1 | 1+t | 1 | |||
si32 | 1 | 1 | ||||
si64 | 1 |
表中没有包含:
- 转换SPFP到float
- 转换DPFP到double
算术型
加法
将操作数A与B加起来,并返回结果。
SPFP | DPFP | i8 | i16 | i32 | i64 | |
---|---|---|---|---|---|---|
packed | _mm_add_ps | _mm_add_pd | _mm_add_epi8 | _mm_add_epi16 | _mm_add_epi32 | _mm_add_epi64 |
scaler | _mm_add_ss | _mm_add_sd | _mm_add_si64[1] |
[1]:操作数类型为__m64
adds 饱和(saturation)加
本加法使得A和B相加的结果不会超出范围。若结果超出整数范围上界,则返回上界,若超出下界,则返回下界。否则返回结果。
i8 | i16 | u8 | u16 | |
---|---|---|---|---|
packed | _mm_adds_epi8 | _mm_adds_epi16 | _mm_adds_epu8 | _mm_adds_epu16 |
减法
将操作数A减去操作数B,并返回结果。
SPFP | DPFP | i8 | i16 | i32 | i64 | |
---|---|---|---|---|---|---|
packed | _mm_sub_ps | _mm_sub_pd | _mm_sub_epi8 | _mm_sub_epi16 | _mm_sub_epi32 | _mm_sub_epi64 |
scaler | _mm_sub_ss | _mm_sub_sd | NULL | NULL | NULL | _mm_sub_si64[1] |
[1]:操作数类型为__m64
subs 饱和减
将A与B相减。若结果超出整数范围上界,则返回上界,若超出下界,则返回下界,否则返回结果。
i8 | i16 | u8 | u16 | |
---|---|---|---|---|
packed | _mm_subs_epi8 | _mm_subs_epi16 | _mm_subs_epu8 | _mm_subs_epu16 |
乘法
操作数A乘以操作数B,并返回结果
SPFP | DPFP | u32 | |
---|---|---|---|
packed | _mm_mul_ps | _mm_mul_pd | _mm_mul_epu32 |
scaler | _mm_mul_ss | _mm_mul_sd | _mm_mul_su32[1] |
[1]:操作数为__m64
,取A和B的最低数相乘,结果为64bit整数,保存在__m64
中
mulhi 乘法并取高位结果
将操作数A与B相乘,得到中间结果,该中间结果位长是乘数的位长的两倍。然后,取出中间结果高一半的比特,放入最终结果。这样做是在乘法溢出时,仍然能够得到更长位数的准确的结果。
i16 | u16 | |
---|---|---|
packed | _mm_mulhi_epi16 | _mm_mulhi_epu16, _mm_mulhi_pu16 |
mullo 乘法并取低位结果
将操作数A与B相乘,取得中间结果,中间结果的长度是A的两倍,然后取中间结果的低位数的结果,该结果长度与A等长。
i16 | |
---|---|
packed | _mm_mullo_epi16 |
除法
操作数A除以操作数B,并返回结果
SPFP | DPFP | |
---|---|---|
packed | _mm_div_ps | _mm_div_pd |
scaler | _mm_div_ss | _mm_div_sd |
平方根
计算操作数A的平方根。
SPFP | DPFP | |
---|---|---|
packed | _mm_sqrt_ps | _mm_sqrt_pd |
scaler | _mm_sqrt_ss | _mm_sqrt_sd[1] |
[1]:此函数输入两个参数A和B,结果的低位是B的平方根,高位是A的高位DPFP。
倒数
倒数
SPFP | |
---|---|
packed | _mm_rcp_ps |
scaler | _mm_rcp_ss |
平方根倒数
先求平方根,然后求其倒数,最后取近似值。
SPFP | |
---|---|
packed | _mm_rsqrt_ps |
scaler | _mm_rsqrt_ss |
最大值
A和B对应位置的数相比较,将较大的放到结果中,并返回
SPFP | DPFP | u8 | i16 | |
---|---|---|---|---|
packed | _mm_max_ps | _mm_max_pd | _mm_max_epu8, _mm_max_pu8 | _mm_max_epi16, _mm_max_pi16 |
scaler | _mm_max_ss | _mm_max_sd |
最小值
A和B对应位置的数相比较,将较小的放到结果中,并返回
SPFP | DPFP | u8 | i16 | |
---|---|---|---|---|
packed | _mm_min_ps | _mm_min_pd | _mm_min_epu8, _mm_min_pu8 | _mm_min_epi16, _mm_min_pi16 |
scaler | _mm_min_ss | _mm_min_sd |
逻辑运算型
运算的函数名遵循一个格式:_mm_<op>_<type>
op
可以是:
- and:与运算
- or:或运算
- andnot:与非,先非A,然后再与B
- xor:异或运算
type
可以是:
- ps
- pd
- si128
数值比较型
比较1
_mm_cmp<比较运算>_<类型>
比较运算有:
- eq:等于
- lt:小于
- le:小于等于
- gt:大于
- ge:大于等于
- neq:不等于
- ngt:不大于
- nge:不大于等于
- nlt:不小于
- nle:不小于等于
- ord:是否都不是NaN
- uord:两个数中是否至少一个NaN
支持类型说明:ps,pd,ss,sd具有所有的比较运算。但是i8,i16,i32仅支持eq,lt,gt。
比较2
比较两个寄存器的最低数,返回布尔结果,1为真,0为假。
格式:_mm_comi<比较运算>_<类型>
比较运算有:
- eq:等于
- lt:小于
- le:小于等于
- gt:大于
- ge:大于等于
- neq:不等于
仅支持sd与ss。
比较3
格式:_mm_ucomi<比较运算>_<类型>
比较两个寄存器的最低数,返回布尔结果,1为真,0为假。与上面的运算不同的地方在,本运算在处理QNaNs时不会引发错误。其余都是一样的。
数据转换型
内嵌原语 | 操作 | 对应SSE指令 |
---|---|---|
__m128 _mm_cvtpi32x2_ps(__m64 a, __m64 b); | 转换a的2个32bit有符号整数与b的2个32bit有符号整数 | composite |
数据读取型
操作 | 含义 | ss | ps | sd | pd | pi[1] | pd1 | si16 | si32 | si64 | si128 |
---|---|---|---|---|---|---|---|---|---|---|---|
load | 读取,需对齐 | 1 | 1 | 1 | 1 | 1 | |||||
loadu | 读取,无需对齐 | 1 | 1 | 1 | 1 | 1 | 1 | ||||
loadr | 反向读取 | 1 | 1 | ||||||||
loadh | 读取到高位数字 | 1 | 1 | ||||||||
loadl | 读取到低位数字 | 1 | 1 | ||||||||
load1 | 读取并复制到所有位置 | 1 | 1 |
[1]:本函数有两个操作数,A与B。将会复制B到结果的对应的位置(根据是loadh还是loadl),其余的位置从A中对应位置读取。
特殊函数
_mm_load_pd1
与_mm_load_ps1
:本函数读取传入的地址的一个数,并放到目标的所有位置
数据设置型
内嵌原语 | 操作 | 对应SSE指令 |
---|---|---|
__m128 _mm_set_ss(float w); | 读取w到结果最低位,其余为0 | Composite |
__m128 _mm_set1_ps(float w); | 读取w,并把所有位都设置为w | Composite |
__m128 _mm_set_ps(float z, float y, float x, float w); | 读取z、y、x、w,从最高位到最低位设置数值 | Composite |
__m128 _mm_setr_ps(float z, float y, float x, float w); | 读取z、y、x、w,从最低位到最高位设置数值 | Composite |
__m128 _mm_setzero_ps(void); | 返回全为0的结果 | Composite |
set 设置全部数字
函数参数为所需要设置的所有数字,返回设置完参数的对应数据结构
格式为:_mm_set_<type>
type
可以是:
- epi16
- epi32
- epi64:接收
__m64
类型的参数 - epi64x:接收
__int64
类型的参数 - epi8
- pd
- pd1:将目标寄存器的所有位置都设置为一个数
- ps
- ps1
- sd
- ss
set1 设置所有位置为1个数字
格式为:_mm_set1_<type>
type
可以是:
- epi16
- epi32
- epi64
- epi64x
- epi8
- pd
- ps
setr 与set的设置方向相反
格式:_mm_setr_<type>
type
为:
- epi16
- epi32
- epi64
- epi8
- pd
- ps
数据存储型
内嵌原语 | 操作 | 对应SSE指令 |
---|---|---|
void _mm_storeh_pi(__m64 *p, __m128 a); | 保存2个a的高位SPFP到p | MOVHPS mem, reg |
void _mm_storel_pi(__m64 *p, __m128 a); | 保存2个a的低位SPFP到p | MOVLPS mem, reg |
void _mm_store_ss(float * p, __m128 a); | 保存a的最低位SPFP到p | MOVSS |
void _mm_store1_ps(float * p, __m128 a); | 保存a的最低位SPFP到p数组的前4个位置 | Shuffling + MOVSS |
void _mm_store_ps(float *p, __m128 a); | 保存a的4个SPFP到p数组,p地址必须16字节对齐 | MOVAPS |
void _mm_storeu_ps(float *p, __m128 a); | 保存a的4个SPFP到p数组,p地址不须16字节对齐 | MOVUPS |
void _mm_storer_ps(float * p, __m128 a); | 以相反方向保存a的4个SPFP到p数组,p必须16字节对齐 | MOVAPS + Shuffling |
缓存操作型
内嵌原语 | 操作 | 对应SSE指令 |
---|---|---|
void _mm_prefetch(char const*a, int sel); | 读取1个缓存行到一个靠近处理器的地方,sel定义了预读取的类型 | PREFETCH |
void _mm_stream_pi(__m64 *p, __m64 a); | 保存a的数据到p而不影响当前缓存数据,需要事先清空MMX寄存器的状态 | MOVNTQ |
void _mm_stream_ps(float *p, __m128 a); | 保存a的数据到p而不影响当前缓存数据,p必须16字节对齐 | MOVNTPS |
void _mm256_stream_ps(float *p, __m256 a); | 保存a的数据到p而不影响当前缓存数据,p必须是32字节对齐的 | VMOVNTPS |
void _mm_sfence(void); | 确保后续的存储操作进行之前,前面所有的保存操作是全局可见的 | SFENCE |
预读取资料:https://software.intel.com/sites/default/files/article/326703/5.3-prefetching-on-mic-4.pdf
整数型
内嵌原语 | 操作 | 对应SSE指令 |
---|---|---|
int _mm_extract_pi16(__m64 a, int n); | 根据n的值提取16bit整数,0时取a0,以此类推 | PEXTRW |
__m64 _mm_insert_pi16(__m64 a, int d, int n); | 根据n的值,插入d到a中,0时插入到a0位置,其余不变 | PINSRW |
__m64 _mm_max_pi16(__m64 a, __m64 b); | 比较ab的每一位,取最大值放入结果 | PMAXSW |
__m64 _mm_max_pu8(__m64 a, __m64 b); | 比较ab的每一位无符号8bit整数,取最大值放入结果 | PMAXUB |
__m64 _mm_min_pi16(__m64 a, __m64 b); | 取ab中的最小值放入结果 | PMINSW |
__m64 _mm_min_pu8(__m64 a, __m64 b); | 取ab中的最小值放入结果 | PMINUB |
__m64 _mm_movemask_pi8(__m64 b); | 取b中所有的符号位,依次放入结果中,结果为8bit | PMOVMSKB |
__m64 _mm_mulhi_pu16(__m64 a, __m64 b); | a和b的数字相乘,取32bit结果的高16bit放入最终结果中 | PMULHUW |
__m64 _mm_shuffle_pi16(__m64 a, int n); | 移动a中数字位置,n0和n1比特确定结果最低位是a的哪个数字,n2和n3确定a1,依次类推 | PSHUFW |
void _mm_maskmove_si64(__m64 d, __m64 n, char *p); | 根据n中每一个比特是0还是1,依次确定是否将d的8bit数字放入p的对应位置中,如n0是1,则放d0到p0,如果n1是0,则p1不变 | MASKMOVQ |
__m64 _mm_avg_pu8(__m64 a, __m64 b); | 计算ab对应位置数字的平均值 | PAVGB |
__m64 _mm_avg_pu16(__m64 a, __m64 b); | 计算ab对应位置数字的平均值(?) | PAVGW |
__m64 _mm_sad_pu8(__m64 a, __m64 b); | 计算ab对应数值的差值绝对值的和 | PSADBW |
读写寄存器
内嵌原语 | 操作 | 对应SSE指令 |
---|---|---|
unsigned int _mm_getcsr(void); | 返回控制寄存器的内容 | STMXCSR |
void _mm_setcsr(unsigned int i); | 设置控制寄存器的内容 | LDMXCSR |
杂项
内嵌原语 | 操作 | 对应SSE指令 |
---|---|---|
__m128 _mm_shuffle_ps(__m128 a, __m128 b, unsigned int imm8); | 分别选择ab中的两个64bit数,放入结果中。如imm8为10(2),将会选择a0放到最低位,b1放到最高位。 | SHUFPS |
__m128 _mm_unpackhi_ps(__m128 a, __m128 b); | 将a2,b2,a3,b3放入结果 | UNPCKHPS |
__m128 _mm_unpacklo_ps(__m128 a, __m128 b); | 将a0,b0,a1,b1放入结果 | UNPCKLPS |
__m128 _mm_move_ss( __m128 a, __m128 b); | 设置最低位为b0,其余从a取出 | MOVSS |
__m128 _mm_movehl_ps(__m128 a, __m128 b); | 将b2,b3,a2,a3放入结果 | MOVHLPS |
__m128 _mm_movelh_ps(__m128 a, __m128 b); | 将a0,a1,b0,b1放入结果 | MOVLHPS |
int _mm_movemask_ps(__m128 a); | 构建4bit数,每个bit是对应位置的数的符号位 | MOVMSKPS |