sse指令集
SSE(Streaming SIMD Extensions,單指令多數(shù)據(jù)流擴展)指令集是Intel在Pentium III處理器中率先推出的。其實,早在PIII正式推出之前,Intel公司就曾經(jīng)通過各種渠道公布過所謂的KNI(Katmai New Instruction)指令集,這個指令集也就是SSE指令集的前身,并一度被很多傳媒稱之為MMX指令集的下一個版本,即MMX2指令集。究其背景,原來"KNI"指令集是Intel公司最早為其下一代芯片命名的指令集名稱,而所謂的"MMX2"則完全是硬件評論家們和媒體憑感覺和印象對"KNI"的 評價,Intel公司從未正式發(fā)布過關(guān)于MMX2的消息。
而最終推出的SSE指令集也就是所謂勝出的"互聯(lián)網(wǎng)SSE"指令集。SSE指令集包括了70條指令,其中包含提高3D圖形運算效率的50條SIMD(單指令多數(shù)據(jù)技術(shù))浮點運算指令、12條MMX 整數(shù)運算增強指令、8條優(yōu)化內(nèi)存中連續(xù)數(shù)據(jù)塊傳輸指令。理論上這些指令對目前流行的圖像處理、浮點運算、3D運算、視頻處理、音頻處理等諸多多媒體應(yīng)用起到全面強化的作用。S SE指令與3DNow!指令彼此互不兼容,但SSE包含了3DNow!技術(shù)的絕大部分功能,只是實現(xiàn)的方法不同。SSE兼容MMX指令,它可以通過SIMD和單時鐘周期并行處理多個浮點數(shù)據(jù)來有效地提高浮點運算速度
SSE是英特爾提出的即MMX之后新一代(當然是幾年前了)CPU指令集,最早應(yīng)用在PIII系列CPU上?,F(xiàn)在已經(jīng)得到了Intel PIII、P4、Celeon、Xeon、AMD Athlon、duron等系列CPU的支持。而更新的SSE2指令集僅得到了P4系列CPU的支持,這也是為什么這篇文章是講SSE而不是SSE2的原因之一。另一個原因就是SSE和SSE2的指令系統(tǒng)是非常相似的,SSE2比SSE多的僅是少量的額外浮點處理功能、64位浮點數(shù)運算支持和64位整數(shù)運算支持。
SSE為什么會比傳統(tǒng)的浮點運算更快呢?因為它使用了128位的存儲單元,這對于32位的浮點數(shù)來講,是可以存下4個的,也就是說,SSE中的所有計算都是一次性針對4個浮點數(shù)來完成的,這種批處理當然就會帶來效率的提升。我們再來回顧一下SSE的全稱:Stream SIMD Extentions(流SIMD擴展)。SIMD就是single instruction multiple data,連起來就是“數(shù)據(jù)流單指令多數(shù)據(jù)擴展”,從名字我們就可以更好的理解SSE是如何工作的了。
? 雖然SSE從理論上來講要比傳統(tǒng)的浮點運算會快,但是他所受的限制也很多,首先,雖然他執(zhí)行一次相當于四次,會比傳統(tǒng)的浮點運算執(zhí)行4次的速度要快,但是他執(zhí)行一次的速度卻并沒有想象中的那么快,所以要體現(xiàn)SSE的速度,必須有Stream做前提,就是大量的流數(shù)據(jù),這樣才能發(fā)揮SIMD的強大作用。其次,SSE支持的數(shù)據(jù)類型是4個32位(共計128位)浮點數(shù)集合,就是C、C++語言中的float[4],并且必須是以16位字節(jié)邊界對齊的(稍后會以代碼來進行闡釋,關(guān)于邊界對齊的概念,讀者可以參考論壇上的其它文章,都會有很詳細的解答,我這里就恕不贅述了)。因此這也給輸入和輸出帶來了不少的麻煩,實際上主要影響SSE發(fā)揮性能的就是不停的對數(shù)據(jù)進行復制以適用應(yīng)它的數(shù)據(jù)格式。
? 我是一個C++程序員,對匯編并不很熟,但我又想用SSE來優(yōu)化我的程序,我該怎么做呢?幸好VC++.net為我們提供了很方便的指令C函數(shù)級的封裝和C格式數(shù)據(jù)類型,我們只需像平時寫C++代碼一樣定義變量、調(diào)用函數(shù)就可以很好的應(yīng)用SSE指令了。
? 當然了,我們需要包含一個頭文件,這里面包括了我們需要的數(shù)據(jù)類型和函數(shù)的聲明:
#include |
? SSE運算的標準數(shù)據(jù)類型只有一個,就是:
__m128,它是這樣定義的:
typedef struct __declspec(intrin_type) __declspec(align(16)) __m128 { ?? float m128_f32[4]; } __m128; |
? 簡化一下,就是:
struct __m128 { ?? float m128_f32[4]; }; |
? 比如要定義一個__m128變量,并為它賦四個float整數(shù),可以這樣寫:
__m128 S1 = { 1.0f, 2.0f, 3,0f, 4,0f }; |
? 要改變其中第2個(基數(shù)為0)元素時可以這樣寫:
S1.m128_f32[2] = 6.0f; |
? 令外我們還會用到幾個賦值的指令,它可以讓我們更方便的使用這個數(shù)據(jù)結(jié)構(gòu):
S1 = _mm_set_ps1( 2.0f ); |
? 它會讓S1.m128_f32中的四個元素全部賦予2.0f,這樣會比你一個一個賦值要快的多。
S1 = _mm_setzero_ps(); |
? 這會讓S1中的所有4個浮點數(shù)都置零。
? 還有一些其它的賦值指令,但執(zhí)行起來還沒有自己逐個賦值來的快,只做為一些特殊用途,如果你想了解更多的信息,可以參考MSDN -> VisualC++參考 -> C/C++Language -> C++Language Reference -> Compiler Intrinsics -> MMX, SSE, and SSE2 Intrinsics -> Stream SIMD Extensions(SSE)章節(jié)。
? 一般來講,所有SSE指令函數(shù)都有3個部分組成,中間用下劃線隔開:
_mm_set_ps1 |
? mm表示多媒體擴展指令集
? set表示此函數(shù)的含義縮寫
? ps1表示該函數(shù)對結(jié)果變量的影響,由兩個字母組成,第一個字母表示對結(jié)果變量的影響方式,p表示把結(jié)果做為指向一組數(shù)據(jù)的指針,每一個元素都將參與運算,S表示只將結(jié)果變量中的第一個元素參與運算;第二個字母表示參與運算的數(shù)據(jù)類型。s表示32位浮點數(shù),d表示64位浮點數(shù),i32表示32位定點數(shù),i64表示64位定點數(shù),由于SSE只支持32位浮點數(shù)的運算,所以你可能會在這些指令封裝函數(shù)中找不到包含非s修飾符的,但你可以在MMX和SSE2的指令集中去認識它們。
? 接下來我舉一個例子來說明SSE的指令函數(shù)是如何使用的,必須要說明的是我以下的代碼都是在VC7.1的平臺上寫的,不保證對其它如Dev-C++、Borland C++等開發(fā)平臺的完全兼容。
? 為了方便對比速度,我會用常歸方法和SSE優(yōu)化兩種寫法寫出,并會用一個測試速度的類CTimer來進行計時。
? 這個算法是對一組float值進行放大,函數(shù)ScaleValue1是使用SSE指令優(yōu)化的,函數(shù)ScaleValue2則沒有。我們用10000個元素的float數(shù)組數(shù)據(jù)來測試這兩個算法,每個算法運算10000遍,下面是測試程序和結(jié)果:
#include #include |
class CTimer
{
public:
?????? __forceinline CTimer( void )
?????? {
????????????? QueryPerformanceFrequency( &m_Frequency );
????????????? QueryPerformanceCounter( &m_StartCount );
?????? }
?????? __forceinline void Reset( void )
?????? {
????????????? QueryPerformanceCounter( &m_StartCount );
?????? }
?????? __forceinline double End( void )
?????? {
????????????? static __int64 nCurCount;
????????????? QueryPerformanceCounter( (PLARGE_INTEGER)&nCurCount );
????????????? return double( nCurCount * ( *(__int64*)&m_StartCount ) ) / double( *(__int64*)&m_Frequency );
?????? }
private:
?????? LARGE_INTEGER m_Frequency;
?????? LARGE_INTEGER m_StartCount;
};
void ScaleValue1( float *pArray, DWORD dwCount, float fScale )
{DWORD dwGroupCount = dwCount / 4;
?????? __m128 e_Scale = _mm_set_ps1( fScale );
?????? for ( DWORD i = 0; i < dwGroupCount; i++ )
?????? {
????????????? *(__m128*)( pArray + i * 4 ) = _mm_mul_ps( *(__m128*)( pArray + i * 4 ), e_Scale );
?????? }
}
void ScaleValue2( float *pArray, DWORD dwCount, float fScale )
{
?????? for ( DWORD i = 0; i < dwCount; i++ )
?????? {
????????????? pArray[i] *= fScale;
?????? }
}
#define ARRAYCOUNT 10000
int __cdecl main()
{
?????? float __declspec(align(16)) Array[ARRAYCOUNT];
?????? memset( Array, 0, sizeof(float) * ARRAYCOUNT );
?????? CTimer t;
?????? double dTime;
?????? t.Reset();?for ( int i = 0; i < 100000; i++ )
?????? {
????????????? ScaleValue1( Array, ARRAYCOUNT, 1000.0f );
?????? }
?????? dTime = t.End();
?????? cout << "Use SSE:" << dTime << "秒" << endl;
?????? t.Reset();
?????? for ( int i = 0; i < 100000; i++ )
?????? {
????????????? ScaleValue2( Array, ARRAYCOUNT, 1000.0f );
?????? }
?????? dTime = t.End();
?????? cout << "Not Use SSE:" << dTime << "秒" << endl;
?????? system( "pause" );
?????? return 0;
}
Use SSE:0.997817
Not Use SSE:2.84963
? 這里要注意一下,我使用了__declspec(align(16))做為數(shù)組定義的修釋符,這表示該數(shù)組是以16字節(jié)為邊界對齊的,因為SSE指令只能支持這種格式的內(nèi)存數(shù)據(jù)。
? 我們在這里看到了SSE算法的強大,相信它會成為多媒體程序員手中用來對付無窮盡流媒體數(shù)據(jù)的一把利劍。我后面還會寫一些關(guān)于SSE算法更復雜應(yīng)用的文章,敬請關(guān)注,感謝您抽時間閱讀!
評論
查看更多