一、SIMD
SIMD單指令流多數據流(SingleInstruction Multiple Data,SIMD)是一種採用一個控制器來控制多個處理器,同時對一組數據(又稱“數據向量”)中的每一個分別執行相同的操作從而實現空間上的並行性的技術。在微處理器中,單指令流多數據流技術則是一個控制器控制多個平行的處理微元,例如Intel的MMX、SSE、AVX以及AMD的3D Now!技術,本文只介紹Intel的SIMD技術。
二 、MMX
MMX是由英特爾開發的一種SIMD多媒體指令集,共有57條指令。它於1996年集成在英特爾奔騰(Pentium) MMX處理器上,以提高其多媒體數據的處理能力。
其優點是增加了處理器關於多媒體方面的處理能力,缺點是佔用浮點數寄存器進行運算(64位MMX寄存器實際上就是浮點數寄存器的別名)以至於MMX指令和浮點數操作不能同時工作。爲了減少在MMX和浮點數模式切換之間所消耗的時間,程序員們儘可能減少模式切換的次數,也就是說,這兩種操作在應用上是互斥的。後來英特爾在此基礎上發展出SSE指令集。現在新開發的程序不再僅使用MMX來優化軟件執行效能,而是改使用如SSE、AVX等更容易優化效能的新一代多媒體指令集,不過目前的處理器仍可以執行鍼對MMX優化的較早期軟件。
MMX寄存器,稱作MM0到MM7,實際上就是處理器內部80比特字長的浮點寄存器棧st(0)到st(7)的尾數部分(64比特長)的複用。由於浮點棧寄存器的高16位未被MMX技術使用,因此這16位都置爲1,因此從棧寄存器的角度看,其浮點值爲NaN或Infinities,這可用於區分寄存器是處於浮點棧狀態還是MMX狀態.作爲MMX寄存器都是直接訪問。利用了裝配數據類型(packeddata type)的概念,每個MMX寄存器的64比特字長可以看作是2個32位整數、或者4個16位整數、或者8個8位整數,從而可以執行整數SIMD運算。這對於1990年代中期的2D、3D計算的加速還是很有意義的,因爲當時的計算機的圖形處理器(GPU)還很不發達。但現在MMX整數SIMD運算對於圖形運算來說是多餘的技術了。不過MMX的飽和算術運算(saturationarithmeticoperations)對於一些數字信號處理應用還是有用的。
三、 SSE
繼 MMX技術之後,Intel又於1999年在Pentium-III處理器上推出SSE技術,引入了新的128比特寬的寄存器集 (register file),稱作XMM0到XMM7。這些XMM寄存器用於4個單精度浮點數運算的SIMD執行,並可以與MMX整數運算或x87浮點運算混合執行。 2001年在Pentium 4上引入了SSE2技術,進一步擴展了指令集,使得XMM寄存器上可以執行8/16/32位寬的整數SIMD運算或雙精度浮點數的SIMD運算。這使得 SIMD技術基本完善。
SSE(StreamingSIMD Extensions)是英特爾在其計算機芯片Pentium III中引入的指令集,是繼MMX的擴充指令集。SSE指令集提供了 70條新指令。
SSE寄存器:
SSE 加入新的 8個 28 位緩存器(XMM0~XMM7)。除此之外還有一個新的 32 位的控制/狀態緩存器(MXCSR)。不過只能在 64位的模式下才能使用額外8個緩存器。
每個緩存器可以容納 4個32位單精度浮點數,或是2個 64位雙精度浮點數,或是 4個32位整數,或是 8個16位短整數,或是 16個字符。整數運算能夠使用正負號運算。而整數 SIMD運算可能仍然要與 8個64位MMX緩存器一起執行。
因爲操作系統必須要在進程切換的時候保護這些128位的緩存器狀態,除非操作系統去啓動這些緩存器,否則默認值是不會去啓用的。這表示操作系統必須要知道如何使用FXSAVE與FXRSTOR 指令才能儲存x87 與SSE緩存器的狀態。而在當時IA-32的主流操作系統很快的都加入了此功能。
由於 SSE加入了浮點支持,SSE就比MMX更加常用。而SSE2加入了整數運算支持之後讓SSE更加的有彈性,當MMX變成是多餘的指令集,SSE指令集甚至可以與MMX並行運作,在某些時候可以提供額外的性能增進。
第一個支持 SSE的 CPU是 Pentium III,在FPU與SSE之間共享執行支持。當編譯出來的軟件能夠交叉的同時以FPU與SSE運作,PentiumIII並無法在同一個週期中同時執行FPU與SSE。這個限制降低了指令管線的有效性,不過XMM緩存器能夠讓SIMD與純量浮點運算混合執行,而不會因爲切換MMX/浮點模式而產生性能的折損。
數據類型
Intrinsic使用的數據類型和其寄存器是對應,有
· 64位 MMX指令集使用
· 128位 SSE指令集使用
· 256位 AVX指令集使用
甚至AVX-512指令集有512位的寄存器,那麼相對應Intrinsic的數據也就有512位。
具體的數據類型及其說明如下:
1. **__m64** 64位對應的數據類型,該類型僅能供MMX指令集使用。由於MMX指令集也能使用SSE指令集的128位寄存器,故該數據類型使用的情況較少。
2. **__m128 / __m128i / __m128d** 這三種數據類型都是128位的數據類型。由於SSE指令集即能操作整型,又能操作浮點型(單精度和雙精度),這三種數據類型根據所帶後綴的不同代表不同類型的操作數。__m128是單精度浮點數,__m128i是整型,__m128d是雙精度浮點數。
3. 256和512的數據類型和128位的類似,只是存放的個數不同,這裏不再贅述。
知道了各種數據類型的長度以及其代碼的意義,那麼它的表現形式到底是怎麼樣的呢?看下圖
__m128i yy;
yy是__m128i型,從上圖可以看出__m128i是一個聯合體(union),根據不同成員包含不同的數據類型。看其具體的成員包含了8位、16位、32位和64位的有符號/無符號整數(這裏__m128i是整型,故只有整型的成員,浮點數的使用__m128)。而每個成員都是一個數組,數組中填充着相應的數據,並且根據數據長度的不同數組的長度也不同(數組長度 = 128 / 每個數據的長度(位))。在使用的時候一定要特別的注意要操作數據的類型,也就是數據的長度,例如上圖同一個變量yy當作4個32位有符號整型使用時其數據是:0,0,1024,1024;但是當做64位有符號整型時其數據爲:0,4398046512128,大大的不同
Intrinsic 函數的命名
Intrinsic函數的命名也是有一定的規律的,一個Intrinsic通常由3部分構成,這個三個部分的具體含義如下:
1. 第一部分爲前綴_mm,表示是SSE指令集對應的Intrinsic函數。_mm256或_mm512是AVX,AVX-512指令集的Intrinsic函數前綴,這裏只討論SSE故略去不作說明。
2. 第二部分爲對應的指令的操作,如_add,_mul,_load等,有些操作可能會有修飾符,如loadu將未16位對齊的操作數加載到寄存器中。
3. 第三部分爲操作的對象名及數據類型,_ps packed操作所有的單精度浮點數;_pd packed操作所有的雙精度浮點數;_pixx(xx爲長度,可以是8,16,32,64)packed操作所有的xx位有符號整數,使用的寄存器長度爲64位;_epixx(xx爲長度)packed操作所有的xx位的有符號整數,使用的寄存器長度爲128位;_epuxx packed操作所有的xx位的無符號整數;_ss操作第一個單精度浮點數。....
將這三部分組合到以其就是一個完整的Intrinsic函數,如_mm_mul_epi32對參數中所有的32位有符號整數進行乘法運算。
SSE指令集對分支處理能力非常的差,而且從128位的數據中提取某些元素數據的代價又非常的大,因此不適合有複雜邏輯的運算。
後續版本:
SSE2是 Intel在Pentium 4處理器的最初版本中引入的。SSE2指令集添加了對64位雙精度浮點數的支持,以及對整型數據的支持,也就是說這個指令集中所有的MMX指令都是多餘的了,同時也避免了佔用浮點數寄存器。這個指令集還增加了對CPU快取的控制指令。Intel後來在其Intel 64架構中也增加了對x86-64的支持。
SSE3是 Intel在Pentium 4處理器的 Prescott核心中引入的第三代SIMD指令集。這個指令集擴展的指令包含寄存器的局部位之間的運算,例如高位和低位之間的加減運算;浮點數到整數的轉換,以及對超線程技術的支持
SSSE3是Intel針對SSE3指令集的一次額外擴充,最早內建於Core2 Duo處理器中。
SSE4是Intel在Penryn核心的Core 2 Duo與Core2 Solo處理器時,新增的47條新多媒體指令集,並且現在更新至SSE4.2
AVX是Sandy Bridge和Larrabee架構新指令集,Intel的微架構也進入了全速發展的時期,在2010年4月結束的IDF峯會上Intel公司就發佈了2010年的RoadMap。2011年1月Intel發佈全新的處理器微架構Sandy Bridge,其中全新增加的指令集也將帶來CPU性能的提
升。AVX(AdvancedVector Extensions)是Intel的SSE延伸架構,如IA16至IA32般的把緩存器XMM 128bit提升至YMM 256bit,以增加一倍的運算效率。此架構支持了三運算指令(3-OperandInstructions),減少在編碼上需要先複製才能運算的動作。在微碼部分使用了LES LDS這兩少用的指令作爲延伸指令Prefix。
SSE sample vec_add.cpp
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
#include<sys/time.h>
#include<stdbool.h>
#include<xmintrin.h>//SSE指令需要的頭文件
#define N 4*100000
float op1[N] __attribute__((aligned(32)));//內存對齊
float op2[N] __attribute__((aligned(32)));
float result1[N] __attribute__((aligned(32)));
float result2[N] __attribute__((aligned(32)));
void init()
{
for(int i = 0;i < N; i++){
op1[i] = (float)rand()/(float)RAND_MAX;
op2[i] = (float)rand()/(float)RAND_MAX;
}
}
void checkResult(int debug)
{
bool isSame = true;
for(int i = 0;i < N; i++)
{
if (debug){
printf("%lf %lf\n", result1[i], result2[i]);
}
else{
if (fabs(result1[i] - result2[i]) > 0.000001){
isSame = false;
break;
}
}
}
if (!debug) {
if (isSame)
printf("Result is Same\n");
else
printf("Result is not same\n");
}
}
void add1()
{
for(int i = 0; i < N;i++)
result1[i] = op1[i] + op2[i];
}
void add2()
{
__m128 a;
__m128 b;
__m128 c;
for(int i = 0; i < N;i = i + 4)
{
//從op1+i執向的內存中,加載4個float數到寄存器
a = _mm_load_ps(op1 + i);
b = _mm_load_ps(op2 + i);
c = _mm_add_ps(a, b);//並行計算加4個float數據
//將寄存器中的 4個float數存儲到c指向內存中
_mm_store_ps(result2 + i, c);
}
}
int main(int argc, char* argv[])
{
init();
srand((unsigned int)time(NULL));
printf("Add a vector:\n");
struct timeval start;
struct timeval end;
gettimeofday(&start,NULL);
add1();
gettimeofday(&end,NULL);
float time_use=1000.0*(float)(end.tv_sec-start.tv_sec)+(float)(end.tv_usec-start.tv_usec)/1000.0;
printf("add a vector Time use =%f(ms)\n",time_use);
printf("\n");
printf("Add a vector with SSE instructions:\n");
gettimeofday(&start,NULL);
add2();
gettimeofday(&end,NULL);
time_use=1000.0*(float)(end.tv_sec-start.tv_sec)+(float)(end.tv_usec-start.tv_usec)/1000.0;
printf("add a vector with sse Time use =%f(ms)\n",time_use);
printf("\n");
checkResult(0);
//test();
return 0;
}
編譯:
g++ vec_add.cpp -msse -o vec_add
執行結果:
四、自動向量化
使用SSE、AVX的指令可以提高CPU的計算性能,但是學習和使用SIMD指令都需要成本。自動向量話只需要在編譯時加入自動向量話指令,當然性能和使用SIMD指令要稍差些。
自動向量化編譯選項:(gcc編譯器)
-O3(使用O3默認打開自動向量化)
-ftree-vectorize(打開自動向量化)
-ftree-vectorizer-verbose=n(編譯時輸出向量化信息,n是整數,3和9常用)
參考:
1、https://software.intel.com/sites/landingpage/IntrinsicsGuide/#
2、http://blog.csdn.net/conowen/article/details/7255920
3、https://gcc.gnu.org/projects/tree-ssa/vectorization.html#using