SIMD指令學習筆記


SIMD發展

所謂的SIMD指令,指的是single instruction multiple data,即單指令多數據運算,其目的就在於幫助CPU實現數據並行,提高運算效率。

MMX

MMX是由57條指令組成的SIMD多媒體指令集,MMX將64位寄存當作2個32位或8個8位寄存器來用,只能處理整形計算,這樣的64位寄存器有8組,分別命名為MM0~MM7.這些寄存器不是為MMX單獨設置的,而是借用的FPU的寄存器,占用浮點寄存器進行運算(64位MMX寄存器實際上就是浮點數寄存器的別名),以至於MMX指令和浮點數操作不能同時工作。為了減少在MMX和浮點數模式切換之間所消耗的時間,程序員們盡可能減少模式切換的次數,也就是說,這兩種操作在應用上是互斥的。

SSE

SSE全稱是Streaming SIMD Extensions,是一種在MMX基礎上發展出來的SIMD指令集,其不再占用浮點寄存器,而是使用單獨的128位XMM寄存器。在此基礎上又發展除了SSE2/SSE3/SSE4指令集。SSE2則進一步支持雙精度浮點數,由於寄存器長度沒有變長,所以只能支持2個雙精度浮點計算或是4個單精度浮點計算,另外,它在這組寄存器上實現了整型計算,從而代替了MMX。SSE3支持一些更加復雜的算術計算。SSE4增加了更多指令,並且在數據搬移上下了一番工夫,支持不對齊的數據搬移,增加了super shuffle引擎等。

AVX

在SSE指令集的基礎上將128位的XMM寄存器擴展為長度為256位的YMM寄存器,使其支持256位的矢量計算,並且AVX全面兼容SSE/SSE2/SSE3/SSE4,也就是YMM寄存器的低128位就是XMM寄存器。

3DNow!

3DNow!是對於Intel MMX寄存器的邏輯拓展,MMX僅提供了並行的整數操作,3DNow!實現了並行浮點操作。3DNow!在現有MMX指令集基礎上拓展可以做到混合操作整數代碼和浮點代碼,同時不需要MMX必須的上下文轉換。

SIMD指令支持

要使用SIMD指令集,需要同時獲得處理器和編譯器的支持,以我目前的電腦為例:

查看CPU支持的SIMD指令集

cat /proc/cpuinfo

可以看到編譯器支持的SIMD指令集:

......
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb invpcid_single pti ssbd ibrs ibpb stibp tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid mpx rdseed adx smap clflushopt intel_pt xsaveopt xsavec xgetbv1 xsaves dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp md_clear flush_l1d
......

查看GCC處理器支持的SIMD指令集

gcc -march=native -c -Q --help=target

可以看到支持和不支持的SIMD指令集:

The following options are target specific:
......
  -mavx                       		[enabled]
  -mavx2                      		[enabled]
  -mavx256-split-unaligned-load 	[disabled]
  -mavx256-split-unaligned-store 	[disabled]
  -mavx512bw                  		[disabled]
  -mavx512cd                  		[disabled]
  -mavx512dq                  		[disabled]
  -mavx512er                  		[disabled]
  -mavx512f                   		[disabled]
  -mavx512ifma                		[disabled]
  -mavx512pf                  		[disabled]
  -mavx512vbmi                		[disabled]
  -mavx512vl                  		[disabled]
......	
  -mmmx                       		[enabled]
......
  -msse                       		[enabled]
  -msse2                      		[enabled]
  -msse2avx                   		[disabled]
  -msse3                      		[enabled]
  -msse4                      		[enabled]
  -msse4.1                    		[enabled]
  -msse4.2                    		[enabled]
  -msse4a                     		[disabled]
  -msse5                      		
  -msseregparm                		[disabled]
  -mssse3                     		[enabled]
......

SIMD指令

MMX指令

寄存器

MMX使用8個64位的通用寄存器MM0-MM7,這些寄存器中每個都可以作為一個64位,也可以作為2個32位,4個16位或者8個8位使用。

模式

由於MMX和FPU寄存器共享相同的空間,因此MMX代碼和浮點代碼就不能同時使用,所以也就有MMX模式和FPU模型。從FPU模式進入MMX模式比較簡單,就是直接執行一個MMX指令即可。但是退出MMX模式進入FPU模式就不那么簡單,這里要使用一個指令:

emms

該指令沒有任何參數,可以在任何時候執行。使用該指令就可以恢復FPU從可以正常執行浮點指令。

指令與指令形式

MMX的指令除了幾個特殊的,幾乎都是以字母p開頭的。

大多數指令結尾處標明的操作數的位數:

b:byte,字節,8位

w:word,雙字節,字,16位

d:doubleword,雙字,32位

q:quadwords,四字,64位

不以p開頭的指令

從MMX模式進入FPU模式:

emm

32位數據移動,在內存和寄存器或者寄存器之間,移動之后,寄存器高32位會置為0:

movd

在寄存器之間或者內存和寄存器之間移動64位數據:

movq
以p開頭的指令

p+功能+其他+位數

paddb    ;無符號
paddsb    ;有符號
paddusb   ;無符號
paddw
paddsw
paddusw
paddd

SSE指令

寄存器

SSE指令有8個128位寄存器XMM0-XMM7,每個寄存器可以存放4個32位單精度浮點數,使用這些寄存器就可以完成SSE的浮點運算指令。此外,還有一個控制寄存器MXCSR。MXCSR寄存器是一個32位的關於SSE指令控制狀態信息的一個32位寄存器。

模式

按照運算方式的不同將SSE指令分成Packed和Scalar兩類:

Packed方式:這種方式的SSE指令,對於XMM寄存器中四個浮點數都對應進行計算

Scalar方式:這種方式的SSE指令,只對XMM寄存器最低的一個浮點數進行計算

指令與指令形式

SSE指令的指令格式由三部分組成:指令+模式+s,比如addps,就代表xmm寄存器四個單精度浮點數都分別相加。

算數指令
addps   ;加法,4個32位全加
addss   ;加法,低32位加,其他不變
mulps   ;乘法,
mulss   ;乘法,
比較指令
cmpxxps   ;比較4個單精度浮點數。
cmpxxss   ;比較最低的兩個單精度浮點數。
comiss   ;比較最低的兩個單精度浮點值,並且將結果存到EFLAGS。
ucomiss   ;同上,不同指出是不會QNaNs拋出異常。
邏輯指令
andps   ;邏輯與4個單精度浮點數與其他4個單精度浮點數。
轉換指令
cvtpi2ps   ;將2個32位整數轉換為32位浮點數。
cvtps2pi   ;與上條指令相反,
cvtsi2ss   ;將一個32位整數轉換為32位浮點數,前邊的3個值保持不變。
cvtss2si   ;與上條指令相反。
cvttps2pi  ;使用截斷,將2個32位浮點值轉換為32位整數。
cvttss2si  ;與上條指令相反。
Load/Store指令
movaps   ;移動128位值。
movhlps   ;移動高一半到低一半
movhps   ;移動64位值到xmm寄存器的高一半。.
movss   ;移動低位的單精度浮點數,如果目的地址是另外的Xmm寄存器,高位3個浮點數保持不變,否則置為0。
shuffle指令
shufps   ;打亂4個單精度浮點數

SSE2

SSE2是SSE指令的升級版,寄存器與指令格式都和SSE一致,不同之處在於其能夠處理雙精度浮點數等更多數據類型。

addpd   ;2個64位雙精度浮點數分別相加
addsd   ;只加低64位雙精度浮點數
mulpd
mulsd

paddq   ;2個64位整數相加
cmppd   ;比較2對64位double
cmpsd   ;比較處於低64位的double

movq   ;移動一個64位的值,清空XMM寄存器的高64位
movsd   ;移動一個64位的double,如果在兩個XMM寄存器之間移動那么就保持高64位不變
movapd   ;移動兩個對其的64位double
movupd   ;移動兩個不對其的64位double
movhpd   ;移動到XMM寄存器或者從XMM寄存器移出高64位值
movlpd   ;移動到XMM寄存器或者從XMM寄存器移出低64位值
movdq2q  ;Moves bottom 64bit value into an MMX register.
movq2dq  ;Moves an MMX register value to the bottom of an XMM register. Top is cleared to zero.
movntpd  ;Moves a 128bit value to memory without using the cache. NT is "Non Temporal."
movntdq  ;Moves a 128bit value to memory without using the cache.
movnti   ;Moves a 32bit value without using the cache.

clflush   ;從所有級別的cache刷新cache line

SSE3

SSE3增加了13條新的指令。

addsubpd   ;高位的兩個double相加,低位的兩個double相減
addsubps   ;對於打包單精度浮點數,對第2個和第4個32位執行加法,第1和第3個32位執行減法
haddpd   ;結果高位的double是第一個操作數的高位和低位的和,低位的double是第二個操作數的高位和低位和
haddps    ;兩個操作數四個單精度浮點數分別相加

lddqu   ;加載一個不對其的128位值
movddup   ;將64位值同時加載進128位寄存器的高位和低位
movshdup   ;Duplicates the high singles into high and low singles.
movsldup   ;Duplicates the low singles into high and low singles.
fisttp   ;用截斷的方式轉換一個浮點值到一個整數

3DNow!指令

寄存器

3DNow!指令的寄存器和MMX一樣,都是和FPU寄存器共享空間的8個64位寄存器,在3DNow中,如果當做浮點單元使用,每一個寄存器都存儲兩個32位的浮點值,如果當做整數單元使用,同MMX。

模式

3DNow和MMX使用相同的空間,因此同樣可以使用:

emms

指令進行清除,該指令用在從MMX/3DNow轉換到一般浮點寄存器模式。

除了以上這個指令,3DNow增加了一個比emms更快的指令:

femms

作用相同,速度更快。

指令與指令形式

大多數指令都是以p開頭的。

浮點指令
pfmax   ;獲取兩個浮點數中大者,第一個參數是寄存器,第二個參數是內存或者寄存器,最終結果存儲在第一個寄存器中。
pfcmpeq   ;比較兩個32位浮點數是否相等。參數,運算結果情況同上。
pfadd   ;加法指令,參數,運算結果情況同上。
pfmul   ;同上。
轉換指令

3DNow提供了在整數和浮點數類型之間的轉換指令。

pi2fd   ;第一個參數是浮點數值,第二個參數是MMX寄存器或者一個內存位置,該指令將整數轉換為浮點數。
pf2id   ;將浮點數轉換為整數
整數指令

雖然3DNow!主要是針對浮點運算的,但是3DNow還是在MMX基礎上增加了整數指令:

pavgusb
pmulhrw
其他指令

3DNow!增加了兩個緩存管理的指令,這兩個指令的作用是相同的,都是將數據加載進緩存,不同的是后者同時准備將數據寫回。參數都是一個,即加載數據的地址。一個完全的cache line被加載,都至少是32位。

prefetch
prefetchw

AVX

寄存器

YMM0-YMM15一共16個256位寄存器,XMM寄存器被映射到YMM寄存器的高一半的位置。

指令與指令形式

AVX重現了許多SSE指令,但是也增加了一些新的指令:

VBROADCAST[S|D|F128]   ;賦值一個32到128位操作數到一個寄存器的所有域
VINSERTF128   ;替代一個YMM寄存器的高一半或者低一半
VEXTRACT128   ;賦值一個YMM寄存器的高或者低一半
VMASKMOVP[S|D]   ;條件移動

SIMD指令的使用

對於SIMD指令集的使用,有如下三種方式:

編譯器優化

即使用C/C++編寫程序之后,帶有SIMD優化選項編譯,在CPU支持的情況下,編譯器按照自己的規則去優化。

使用intrinsic指令

參考Intel手冊,針對SIMD指令,可以在編程時直接使用其內置的某些庫函數,編譯的時候在cpu和編譯器的支持下會生成對應的SIMD指令。

比如:

double _mm_cvtsd_f64 (__m128d a)

該函數編譯時就會翻譯成指令:

movsd

嵌入式匯編

內聯匯編直接在程序中嵌入對應的SIMD指令。

SIMD指令使用舉例

簡單浮點數矩陣乘法(編譯器優化)

float_matrix_mutiply.c:

#include <stdio.h>
#include <stdio.h>
void printf_matrix(int x,int y,double a[x][y])
{
        for(int i=0;i<x;i++){
                for(int j=0;j<y;j++){
                        printf("%lf ",a[i][j]);
                }
                printf("\n");
        }
}

int main()
{
        int M=2,N=3,P=4;
        double matrix1[M][N];
        double matrix2[N][P];

        FILE *fp1,*fp2;
        fp1 = fopen("./matrix1","r");
        fp2 = fopen("./matrix2","r");

        int i = 0;
        while(!feof(fp1)){
                fscanf(fp1,"%lf %lf %lf",&matrix1[i][0],&matrix1[i][1],&matrix1[i][2]);
                i++;
        }
        printf("The first matrix is:\n");
        printf_matrix(M,N,matrix1);

        int j = 0;
        while(!feof(fp2)){
                fscanf(fp2,"%lf %lf %lf %lf",&matrix2[j][0],&matrix2[j][1],&matrix2[j][2],&matrix2[j][3]);
                j++;
        }
        printf("The second matrix is:\n");
        printf_matrix(N,P,matrix2);

        double result[M][P];
        for(int i=0;i<M;i++){
                for(int k=0;k<P;k++){
                        for(int j=0;j<N;j++){
                                result[i][k] += matrix1[i][j]*matrix2[j][k];
                        }
                }
        }
        printf("The matrix mutilpy result is:\n");
        printf_matrix(M,P,result);
}

編譯:

gcc float_matrix_mutiply.c -o float_matrix_mutiply -msse -mfpmath=sse -march=native

反編譯可以看到使用了xmm寄存器以及movsd這樣的SIMD指令:​

數組對應位置求和(intrinsic指令)

程序:

#include <stdio.h>
#include <x86intrin.h>

int main()
{
    float __attribute__((aligned(16))) a[4]={1.0,2.0,3.0,4.0};
    float __attribute__((aligned(16))) b[4]={4.0,3.0,2.0,1.0};
    __m128 A=_mm_load_ps(a);
    __m128 B=_mm_load_ps(b);
    __m128 C=_mm_add_ps(A,B);
    for(int i=0;i<4;i++)
        printf("%f ",C[i]);
    printf("\n");
    return 0;
}

編譯:

gcc time.c -o time -msse -m32

反匯編:

參考

Intel Intrinsics Guide

difference between MMX and XMM register?

SIMD指令初學

MMX

SIMD指令集

Streaming SIMD Extensions (SSE)

Streaming SIMD Extensions 2 (SSE2)

Streaming SIMD Extensions 3 (SSE3)

AVX Instruction Set

MultiMedia eXtensions

3DNow! Instruction Set

[使用Intel SSE/AVX指令集(SIMD)加速向量內積計算](


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM