SIMD
Table of Contents
- 1. 指令集
- 2. 基本概念
- 3. 指令
- 3.1. Data Transfer Instructions
- 3.2. Arithmetic Instructions
- 3.3. Comparison Instructions
- 3.4. Conversion Instructions
- 3.5. Insert & Unpack Instructions
- 3.6. Logical Instructions
- 3.7. Shift Instructions
- 3.8. Shuffle Instructions
- 3.9. Blending Instructions
- 3.10. String Instructions
- 3.11. MISC Instructions
从Pentium II和Pentium MMX处理器系列开始,芯片开始支持SIMD操作. SIMD全称是Single Instruction Multiple Data,即单指令多数据, 可以在一个周期内操作多个数据.
1 指令集
1.1 MMX
- Multimedia Extensions.[PII & PMMX]
- (CPUID.01H:EDX.MMX[bit 23]=1)
1.2 SSE.
- Streaming SIMD Extensions.[PIII]
- (CPUID.01H:EDX.SSE[bit 25]=1)
1.3 SSE2
- Streaming SIMD Extensions2.[P4 & Xeon]
- (CPUID.01H:EDX.SSE2[bit 26]=1)
1.4 SSE3
- Streaming SIMD Extensions3.[P4(HT)]
- (CPUID.01H:ECX.SSE3[bit 0]=1)
1.5 SSSE3
- Supplemental Stream SIMD Extensions3.[Xeon(5100) & Core2]
- (CPUID.01H:ECX.SSSE3[bit 9]=1)
1.6 SSE4
- Streaming SIMD Extensions4.[Xeon(5400) & Core2Ex(QX9650)]
- SSE4.1.(CPUID.01H:ECX.SSE4_1[bit 19]=1)
- SSE4.2.(CPUID.01H:ECX.SSE4_2[bit 20]=1)
1.7 检测代码
/*************************************************************************** * * Copyright (c) Baidu.com, Inc. All Rights Reserved * **************************************************************************/ /** * @file detect.c * @author zhangyan04(@baidu.com) * @brief * */ #include <stdio.h> int check_support_mmx(){ int res=0; __asm__ __volatile__( "movl $1,%%eax\n\t" "cpuid\n\t" "test $0x800000,%%edx\n\t" "jz 1f\n\t" "movl $1,%0\n\t" "1:\n\t" :"=m"(res) ::"eax","edx"); return res; } int check_support_sse(){ int res=0; __asm__ __volatile__( "movl $1,%%eax\n\t" "cpuid\n\t" "test $0x02000000,%%edx\n\t" "jz 1f\n\t" "movl $1,%0\n\t" "1:\n\t" :"=m"(res) ::"eax","edx"); return res; } int check_support_sse2(){ int res=0; __asm__ __volatile__( "movl $1,%%eax\n\t" "cpuid\n\t" "test $0x04000000,%%edx\n\t" "jz 1f\n\t" "movl $1,%0\n\t" "1:\n\t" :"=m"(res) ::"eax","edx"); return res; } int check_support_sse3(){ int res=0; __asm__ __volatile__( "movl $1,%%eax\n\t" "cpuid\n\t" "test $0x1,%%ecx\n\t" "jz 1f\n\t" "movl $1,%0\n\t" "1:\n\t" :"=m"(res) ::"eax","edx"); return res; } int check_support_ssse3(){ int res=0; __asm__ __volatile__( "movl $1,%%eax\n\t" "cpuid\n\t" "test $0x0200,%%ecx\n\t" "jz 1f\n\t" "movl $1,%0\n\t" "1:\n\t" :"=m"(res) ::"eax","edx"); return res; } int check_support_sse4_1(){ int res=0; __asm__ __volatile__( "movl $1,%%eax\n\t" "cpuid\n\t" "test $0x80000,%%ecx\n\t" "jz 1f\n\t" "movl $1,%0\n\t" "1:\n\t" :"=m"(res) ::"eax","edx"); return res; } int check_support_sse4_2(){ int res=0; __asm__ __volatile__( "movl $1,%%eax\n\t" "cpuid\n\t" "test $0x0100000,%%ecx\n\t" "jz 1f\n\t" "movl $1,%0\n\t" "1:\n\t" :"=m"(res) ::"eax","edx"); return res; } int main(){ printf("MMX[%s]\n",check_support_mmx()?"OK":"FAILED"); printf("SSE[%s]\n",check_support_sse()?"OK":"FAILED"); printf("SSE2[%s]\n",check_support_sse2()?"OK":"FAILED"); printf("SSE3[%s]\n",check_support_sse3()?"OK":"FAILED"); printf("SSSE3[%s]\n",check_support_ssse3()?"OK":"FAILED"); printf("SSE4.1[%s]\n",check_support_sse4_1()?"OK":"FAILED"); printf("SSE4.2[%s]\n",check_support_sse4_2()?"OK":"FAILED"); return 0; }
2 基本概念
2.1 %mm寄存器
%mm寄存器是64bit,共有8个%mm寄存器.需要注意的是,%mm0-%mm7是X87 FPU寄存器的alias, 分别对应%r0-%r7.所以对%mm0-%mm7的操作会覆盖X87 FPU的内容.使用%mm寄存器的时候, 效果是这样的.
- TOS(Top Of Stack)会被置为0,也就是FPU registers的顶部会置0.
- 整个FPU tag word会被置为valid(0x0).如果后续想使用的话,需要使用EMMS指令.
- FPU register有80位,但是%mm寄存器只是用了64位,因此其余位填充(0xff).
因此如果在使用%mm寄存器之后,想使用FPU指令的话,那么应该
- fsave/fxsave保存FPU状态.
- 执行EMMS指令.
- 可选地使用frstore/fxstore载入之前FPU状态.
- 执行FPU指令.
如果使用FPU指令之后,想切换回%mm寄存器的话.
- fsave/fxsave保存FPU状态.
- 可选地使用frstore/fxrstore载入之前FPU状态.
- 操作%mm寄存器.
EMMS指令会清除MMX的状态,将FPU tag word进行清空,表示所有的FPU registers都已经清空. 我们必须在执行完成MMX指令之后,如果之后需要使用FPU registers的话,那么需要执行这个指令.
2.2 %xmm寄存器
%xmm寄存器是128bit. Intel64架构下允许访问16个%xmm寄存器. IA-32架构下只允许访问8个%xmm寄存器.
2.3 %mxcsr寄存器
%mxcsr是32bit.%mxcsr寄存器是在SSE指令集引入的,用来控制作用在%xmm寄存器操作的行为, 所有的这些行为都是和浮点相关的,在某种程度上非常类似于X87 FPU tag word. 关于%mxcsr寄存器各个位所表示的意思在这里不细说,可以查看Intel手册得到详细解释. 可以查看Intel Vol.1 10.2.3.%mxcrs默认值是0x1f80.
指令 | 说明 |
---|---|
LDMXCSR | mem->%mxcsr.32bit |
STMXCSR | %mxcsr->mem.32bit |
2.4 Saturation & Wraparound
在进行整数运算的时候,可能会存在out-of-range的情况,结果不能够被目标数所表示.对于 这种溢出处理有下面3种方式.
- Wraparound Arithmetic.
回绕模式.比如8个字节表示257的话,那么就是257-256=1.
- Signed Saturation Arithmetic.
符号位溢出模式.比如8个字节表示257的话,那么会是0x7f=127.
- Unsigned Saturation Arithmetic.
无符号溢出模式.比如8个字节表示257的话,那么会是0xff=255.
对于溢出模式对于一些计算是非常重要的.假设256色的像素如果两个像素相叠加的话, 当然不希望像素值发生回绕.如果溢出的话,通常这个像素保持纯黑或者是纯白.
2.5 General Purpose Register(GPR)
通用寄存器,包括EAX/RAX,EBX/RBX,ECX/RCX等.这些通用寄存器和%mm和%xmm之间的差别是, %mm和%xmm不能够用来存放地址,也就是说不能够将内存地址存放在%mm和%xmm里面然后进行引用.
2.6 X87 FPU
X87 FPU是浮点运算部件,共有8个寄存器,组织方式是堆栈.通常来说对于SIMD并不需要关心 X87 FPU这个部件.但是因为SIMD使用的%mm寄存器是FPU寄存器的alias,所以我们这里需要了解. 后面我们把X87 FPU都称为FPU.
对于FPU会有一个状态,状态包括执行环境和寄存器内容.每个寄存器80bit.在操作%mm寄存器 和执行FPU指令切换之间,我们可能需要保存状态.那么下面就是关于FPU操作状态的指令.
指令 | 说明 |
---|---|
FSAVE | 保存FPU状态,然后重新初始化FPU.84/108字节 |
FRSTORE | FSAVE逆操作. |
FXSAVE | 保存FPU状态/%mm寄存器,%xmm寄存器,%mxscr寄存器.512字节. |
FXRSTORE | FXSAVE逆操作. |
关于如何协调%mm寄存器和FPU寄存器的使用,在%mm寄存器这节有解释.
2.7 Packed & Scalar Instructions
对于SIMD提供了操作packed和scalar指令.我们假设存在两个操作数, 假设是(f00,f01,f02,f03)和(f10,f11,f12,f13)的话,那么
- 如果是packed操作的话,那么操作是(f00 op f01,f01 op f11,f02 op f12,f03 op f13).
- 如果是scalar操作的话,那么操作是(f00,f01,f03,f03 op f13).
也就是说,如果在scalar操作的话,仅仅是操作最后面一个单元,其他单元全部复制.
需要注意的是,在Scalar操作下面
- 单精度浮点是24-bit significand + 8-bit exponent.
- 双精度浮点是53-bit significand + 11-bit exponent.
而在IEEE-754和FPU操作环境下面的的话
- 单精度浮点是24-bit significand + 15-bit exponent.
- 双精度浮点是52-bit significand + 15-bit exponent.
此外SIMD操作浮点数和FPU操作浮点数有些不同,SIMD是直接操作浮点数的Native Format, 而FPU是首先在更高的精度上面操作,然后取舍到Native Format.
2.8 Temporal & NonTemporal Data
待续.需要阅读Intel Vol.3A Memory & Cache Control这节.在Intel Vol.1 10.4.6.2也有介绍.
2.9 Alignment
关于对齐方面,如果使用128bit Memory Operand必须进行16字节的对齐.但是有些例外
- 使用UnAlign的Data Transfer操作,比如MOVUPS/MOVUPD.
- 如果是Scalar Memory Float的话,必须是4字节对齐.
- 如果是Scalar Memory Double的话,必须是8字节对齐.
- 此外还有部分指令字节对齐存在例外,会在响应的指令部分说明.
2.10 Asymmetric & Horizontal Processing
分别是对称处理和水平处理.假设存在操作数(a0,a1,a2,a3)以及(b0,b1,b2,b3). 对于大部分SIMD指令处理都是对称处理,也就是(a0 op b0,a1 op b1,a2 op b2,a3 op b3). 相邻处理就是(a0 op a1,a2 op a3,b0 op b1,b2 op b3).
2.11 Zero Fill & Truncated
对于从内存/寄存器载入到寄存器的话,如果位数不够,通常是占用寄存器的低字节, 除非显式指定.对于寄存器中没有使用的高字节,通常是采用0填充,也就是Zero Fill.:).
而另外一个方面,如果从寄存器传输到内存/寄存器,如果寄存器位数过多的话,那么也 通常只是传输寄存器的低字节,而保留寄存器的高字节,也就是Truncated.:).
3 指令
为了方便表示,我们定义下面缩写和操作.
助记符 | 含义 | 其他 |
---|---|---|
A | Aligned | |
U | UnAligned | |
L | Low | |
H | High/Horizontal | |
B | Byte | |
SB | Signed Byte | |
UB | Unsigned Byte | |
W | Word | |
SW | Signed Word | |
UW | Unsigned Word | |
Q | Quad Word | |
DQ | Double Quad Word | |
F | Float | |
D | Double | |
PS | Packed Single Precision Floating Point | |
SS | Scalar Single Precision Floating Point | |
PD | Packed Double Precision Floating Point | |
SD | Scalar Double Precision Floating Point | |
CMP | Compare | |
STR | String | |
EQ | Equal | |
GT | Greater | |
SLL | Shift Left Logical | |
SRL | Shift Right Logical | |
SRA | Shift Right Arithmetic | |
DUP | Duplicate | |
WAM | Wraparound Mode | |
SSM | Signed Saturation Mode | |
USM | Unsigned Saturation Mode | |
RCP | Reciprocal.RCP(x)=1/x | |
SQRT | Square Root | |
RSQRT | Reciprocal Square Root | |
MSK | Mask | |
CVT | Convert | |
SX | Signed Extend | |
ZX | Zero Extend | |
ROUND | ||
UNPCK | Unpack | |
EXTR | Extract | |
INSR | Insert | |
AND | a && b | |
OR | a or b | |
NAND | !(a && b) | |
XOR | a ^ b | |
SAD | Sum of Absolute Difference. | |
SIGN | SIGN(src,dst)=if(src<0):dst=-dst | |
MADD | MADD((a00,a01),(b00,b01))=(a00*b00)+(a01*b01) | |
ALIGNR | ALIGNR(src,dst,imm)=(src,dst) >> imm | |
AVG | Average | |
ABS | Absolute | |
NT | NonTemporal | |
CVTT | Convert With Truncate | |
UNPCKH | UNPCKH((s00,s01),(d00,d01))=(d01,s01) | |
UNPCKL | UNPCKL((s00,s01),(d00,d01))=(d00,s00) | |
MSB | Most Significant Bit | |
LF | Lowest Float | |
LF2 | Lower 2 Floats | |
LF4 | Lower 4 Floats | |
HF | Highest Float | |
HF2 | Higher 2 Floats | |
LD | Lowest Double | |
HD | Highest Double | |
LDW | Lower Double Word | |
LDW2 | Lower 2 Double Words | |
LDW4 | Lower 4 Double Words | |
LW | Lower Word | |
HW | Higher Wword | |
GPR | General Purpose Resgister |
这里有几点需要注意的
- 对于Move如果使用了错误类型指令的话,会产生性能消耗.Vol.1 11.6.9
- 对于使用SIMD来说,推荐使用caller-save.Vol.1 11.6.10.3
3.1 Data Transfer Instructions
3.1.1 Move Mask Instructions
对于每一个data element的MSB移到GRP.这些指令通常用于分支判定.
指令 | 说明 |
---|---|
PMOVMSKB | |
MOVMSKPS | |
MOVMSKPD |
3.1.2 Move Integer Instructions
指令 | 说明 |
---|---|
MOVD | |
MOVQ | |
MOVDQA | |
MOVDQU | |
LDDQU | 功能和MOVDQU相同 |
关于LDDQU和MOVDQU的差别,可以参看Intel关于LDDQU指令描述,主要还是在某些场景 下面的性能差别,功能上没有任何区别.
3.1.3 Move Float Instructions
指令 | 说明 |
---|---|
MOVAPS | |
MOVUPS | |
MOVSS | |
MOVLPS | 2PS<->LF2(%xmm) |
MOVHPS | 2PS<->HF2(%xmm) |
MOVLHPS | LF2(%xmm1)->HF2(%xmm2) |
MOVHLPS | HF2(%xmm1)->LF2(%xmm1) |
3.1.4 Move Double Instructions
指令 | 说明 |
---|---|
MOVAPD | |
MOVUPD | |
MOVSD | |
MOVLPD | PD<->LD(%xmm) |
MOVHPD | PD<->HD(%xmm) |
3.1.5 Move Duplication Instructions
指令 | 说明 |
---|---|
MOVDDUP | (d0,d1)->(d0,d0) |
MOVSHDUP | (f0,f1,f2,f3)->(f1,f1,f3,f3) |
MOVSLDUP | (f0,f1,f2,f3)->(f0,f0,f2,f2) |
3.1.6 Move NonTemporal Instructions
指令 | 说明 |
---|---|
MOVNTI | |
MOVNTQ | |
MOVNTDQ | |
MOVNTDQA | |
MOVNTPS | |
MOVNTPD | |
MASKMOVQ | @@TODO |
MASKMOVDQU | @@TODO |
3.2 Arithmetic Instructions
3.2.1 ADD Instructions
3.2.2 SUB Instructions
3.2.3 MUL Instructions
指令 | 说明 |
---|---|
PMULLW | (w00,w01,..),(w10,w11,..)->(LW(w00*w10),LW(w01*w11),..) |
PMULHW | (w00,w01,..),(w10,w11,..)->(HW(w00*w10),HW(w01*w11),..) |
PMULHUW | 同上,Unsigned方式. |
PMULLD | (dw00,dw01,..),(dw10,dw11,..)->(LDW(dw00*dw10),LDW(dw01*dw11),..) |
PMULDQ | (dw00,dw01,..),(dw10,dw11,..)->(dw00*dw10,dw01*dw11,..) |
PMULUDQ | 同上,Unsigned方式. |
MULPS | |
MULSS | |
MULPD | |
MULSD |
3.2.4 DIV Instructions
指令 | 说明 |
---|---|
DIVPS | |
DIVSS | |
DIVPD | |
DIVSD |
3.2.5 M?X Instructions
3.2.6 Math Instructions
指令 | 说明 |
---|---|
PABSB | |
PABSW | |
PABSD | |
PAVGB | |
PAVGW | |
PSIGNB | |
PSIGNW | |
PSIGND | |
PMADDUBSW | UB->W.SSM |
PMADDWD | W->DW |
PALIGNR | |
PMULHRSW | @@TODO |
PSADBW | |
MPSADBW | @@TODO |
DPPS | |
DPPD | |
ADDSUBPS | (f00,f01,..),(f10,f11,..)->(f00-f10,f01+f11,..) |
ADDSUBPD | (d00,d01,..),(d10,d11,..)->(d00-d10,d01+d11,..) |
RCPPS | |
RCPSS | |
RSQRTPS | |
RSQRTSS | |
SQRTPS | |
SQRTSS | |
SQRTSD |
3.3 Comparison Instructions
需要注意的是,如果没有特殊说明,比较结果是直接存放在结果数里面的, 不会影响EFLAGS这个寄存器内容.如果比较结果影响了EFLAGS寄存器的话, 那么会使用%EFLAGS来标记.
如果每个比较结果是符合预期的话, 那么目的数对应位数会置0xff,否则会置0x0.
对于CMP的指令,会使用立即数来决定具体使用什么比较方式.关于立即数对应 什么比较方式,可以查看具体指令里面的说明,比如CMPPS,CMPSS,CMPPD,CMPSD.
指令 | 说明 |
---|---|
PCMPEQB | |
PCMPEQW | |
PCMPEQD | |
PCMPEQQ | |
PCMPGTB | |
PCMPGTW | |
PCMPGTD | |
PCMPGTQ | |
CMPPS | |
CMPSS | |
CMPPD | |
CMPSD | |
COMISS | %EFLAGS |
UCOMISS | %EFLAGS |
COMISD | %EFLAGS |
UCOMISD | %EFLAGS |
PTEST |
3.4 Conversion Instructions
对于涉及到浮点数的精度取舍问题,使用%mxccsr寄存器来判断.
如果精度取舍是采用截断方式来进行处理的话,那么指令前缀通常是CVTT.
指令 | 说明 |
---|---|
PACKSSWB | SSM |
PACKSSDW | SSM |
PACKUSDW | USM |
指令 | 说明 |
---|---|
CVTPS2PD | |
CVTPD2PS | |
CVTSS2SD | |
CVTSD2SS | |
CVTPI2PS | |
CVTPS2PI | |
CVTTPS2PI | |
CVTSI2SS | |
CVTSS2SI | |
CVTTSS2SI | |
CVTPI2PD | |
CVTPD2PI | |
CVTTPD2PI | |
CVTSI2SD | |
CVTSD2SI | |
CVTTSD2SI | |
CVTDQ2PS | |
CVTPS2DQ | |
CVTTPS2DQ | |
CVTDQ2PD | |
CVTPD2DQ | |
CVTTPD2DQ |
指令 | 说明 |
---|---|
MOVQ2DQ | |
MOVDQ2Q | |
PMOVSXBW | |
PMOVZXBW | |
PMOVSXBD | |
PMOVZXBD | |
PMOVSXWD | |
PMOVZXWD | |
PMOVSXBQ | |
PMOVZXBQ | |
PMOVSXWQ | |
PMOVZXWQ | |
PMOVSXDQ | |
PMOVZXDQ |
指令 | 说明 |
---|---|
ROUNDPS | |
ROUNDPD | |
ROUNDSS | |
ROUNDSD |
3.5 Insert & Unpack Instructions
指令 | 说明 |
---|---|
PUNPCKHBW | |
PUNPCKHWD | |
PUNPCKHDQ | |
PUNPCKHQDQ | |
PUNPCKLBW | |
PUNPCKLWD | |
PUNPCKLDQ | |
PUNPCKLQDQ | |
UNPCKHPS | |
UNPCKLPS | |
UNPCKHPD | |
UNPCKLPD |
指令 | 说明 |
---|---|
PEXTRB | |
PEXTRW | |
PEXTRD | |
PEXTRQ | |
PINSRB | |
PINSRW | |
PINSRD | |
PINSRQ | |
EXTRACTPS | ->GPR |
INSERTPS |
3.6 Logical Instructions
指令 | 说明 |
---|---|
PAND | |
PANDN | |
POR | |
PXOR | |
ANDPS | |
ANDNPS | |
ORPS | |
XORPS | |
ANDPD | |
ANDNPD | |
ORPD | |
XORPD |
3.7 Shift Instructions
指令 | 说明 |
---|---|
PSLLW | |
PSLLD | |
PSLLQ | |
PSLLDQ | |
PSRLW | |
PSRLD | |
PSRLQ | |
PSRLDQ | |
PSRAW | |
PSRAD |
3.8 Shuffle Instructions
SHUF操作根据imm来决定,dst每个位置的element应该是由 src的哪个位置的element来进行填充的.
指令 | 说明 |
---|---|
PSHUFB | |
PSHUFW | |
PSHUFLW | |
PSHUFHW | |
PSHUFD | |
SHUFPS | |
SHUFPD |
3.9 Blending Instructions
BLEND操作是根据imm来决定,dst每个位置的element应该是从src里面对应位置取出, 还是应该从dst里面对应位置取出.
BLENDV操作和BLEND操作过程一样的,不同的是由%xmm0来决定的而不是由imm来决定.
指令 | 说明 |
---|---|
BLENDPS | |
BLENDPD | |
BLENDVPS | |
BLENDVPD | |
PBLENDVB | |
PBLENDW |
3.10 String Instructions
- 内存操作数不要求字节对齐.
- CMPE/I的E表示explicit显式指定长度,I表示implicit隐式指定长度.
- STRI/STRM的I表示结果是Index,M表示结果是Mask.
指令 | 说明 |
---|---|
PCMPESTRI | |
PCMPESTRM | |
PCMPISTRI | |
PCMPISTRM |
3.11 MISC Instructions
3.11.1 Cache Control Instructions
- CLFLUSH
CLFLUSH是cache line flush,能够将某个内存地址的cache line全部失效.
3.11.2 Prefetch Instructions
对于预取指令的话不会影响程序行为,通常来说会预取32个对齐的字节,但是具体 还是依赖于实现.对于NT数据的话,依然会尽可能地减少Cache的污染.
- PREFETCH0
预取到所有Cache层次.
- PREFETCH1
预取到1级缓存.
- PREFETCH2
预取到2级缓存.
- PREFETCHNTA
???
3.11.3 Memory Ordering Instructions
- SFENCE
在SFENCE之前的Store操作,从全局视图来看,一定在SFENCE之后的Store操作之前完成.
- LFENCE
在LFENCE之前的Load操作,从全局视图来看,一定在LFENCE之后的Load操作之前完成.
- MFENCE
MFENCE结合了SFENCE和LFENCE两个功能.
3.11.4 X87 FPU Instructions
- FISTTP
这条指令非常类似FISTP,是将FPU TOS的浮点数转换成为整数,精度处理使用截断. FISTP需要修改FPU Tag Word设置为截断处理才会有这样的效果,
3.11.5 Thread Sync Instructions
需要注意的是,这些指令都只能够在ring0级别下面运行,对于<ring0的界别是可选运行的.
- MONITOR
设置一块地址区域来监视是否存在write-back-stores的操作.
- MWAIT
等待某块地址区域发生write-back-stores.这块地址区域必须经过MONITOR设置.在等待 这块地址区域写入的时候Logical Processor能够进入optimized state.
3.11.6 其他
- PAUSE
PAUSE指令能够显著改善自旋锁的循环等待期间的性能,同时减少机器的耗能.
- Branch Hints
对于Jcc这样的指令,允许在之前加上2EH,3EH作为Prefix能够进行预取提示. 这个没有特别的助记符,只是在生成的机器代码二进制上略有不同.
- CRC32
CRC32算法的有效实现.
- POPCNT
计算操作数的bit表示中存在多少个1.